show unknown otr fingerprint warining

Daniel Gultsch created

Change summary

gen/de/gultsch/chat/R.java                              |  84 ++-
res/layout/dialog_verify_otr.xml                        |  53 ++
res/layout/fragment_conversation.xml                    |  30 +
res/menu/encryption_choices.xml                         |  17 
src/de/gultsch/chat/crypto/OtrEngine.java               |   4 
src/de/gultsch/chat/entities/Account.java               |  24 +
src/de/gultsch/chat/entities/Contact.java               |  71 ++
src/de/gultsch/chat/entities/Conversation.java          |  44 ++
src/de/gultsch/chat/entities/Message.java               |   4 
src/de/gultsch/chat/persistance/DatabaseBackend.java    |   6 
src/de/gultsch/chat/services/XmppConnectionService.java |  67 --
src/de/gultsch/chat/ui/ConversationActivity.java        |  59 ++
src/de/gultsch/chat/ui/ConversationFragment.java        | 225 ++++++++--
src/de/gultsch/chat/utils/UIHelper.java                 |  37 +
14 files changed, 549 insertions(+), 176 deletions(-)

Detailed changes

gen/de/gultsch/chat/R.java 🔗

@@ -42,24 +42,24 @@ public final class R {
         public static final int section_header=0x7f02000d;
     }
     public static final class id {
-        public static final int account_confirm_password_desc=0x7f0a0019;
-        public static final int account_delete=0x7f0a002d;
-        public static final int account_disable=0x7f0a002e;
-        public static final int account_enable=0x7f0a002f;
+        public static final int account_confirm_password_desc=0x7f0a001c;
+        public static final int account_delete=0x7f0a0035;
+        public static final int account_disable=0x7f0a0036;
+        public static final int account_enable=0x7f0a0037;
         public static final int account_jid=0x7f0a0000;
-        public static final int account_list=0x7f0a0022;
-        public static final int account_password=0x7f0a0016;
-        public static final int account_password_confirm2=0x7f0a001a;
+        public static final int account_list=0x7f0a0027;
+        public static final int account_password=0x7f0a0019;
+        public static final int account_password_confirm2=0x7f0a001d;
         public static final int account_status=0x7f0a0002;
-        public static final int account_usetls=0x7f0a0017;
-        public static final int action_accounts=0x7f0a002a;
-        public static final int action_add=0x7f0a0026;
-        public static final int action_add_account=0x7f0a002c;
-        public static final int action_archive=0x7f0a0029;
-        public static final int action_details=0x7f0a0028;
-        public static final int action_refresh_contacts=0x7f0a0030;
-        public static final int action_security=0x7f0a0027;
-        public static final int action_settings=0x7f0a002b;
+        public static final int account_usetls=0x7f0a001a;
+        public static final int action_accounts=0x7f0a002f;
+        public static final int action_add=0x7f0a002b;
+        public static final int action_add_account=0x7f0a0034;
+        public static final int action_archive=0x7f0a002e;
+        public static final int action_details=0x7f0a002d;
+        public static final int action_refresh_contacts=0x7f0a0038;
+        public static final int action_security=0x7f0a002c;
+        public static final int action_settings=0x7f0a0030;
         public static final int contactList=0x7f0a0006;
         public static final int contact_display_name=0x7f0a0008;
         public static final int contact_jid=0x7f0a0009;
@@ -76,21 +76,29 @@ public final class R {
         public static final int details_jidbox=0x7f0a000f;
         public static final int details_receive_presence=0x7f0a0014;
         public static final int details_send_presence=0x7f0a0013;
-        public static final int edit_account_register_new=0x7f0a0018;
-        public static final int list=0x7f0a0020;
-        public static final int message_body=0x7f0a0024;
-        public static final int message_photo=0x7f0a0023;
-        public static final int message_time=0x7f0a0025;
-        public static final int messages_view=0x7f0a001e;
+        public static final int edit_account_register_new=0x7f0a001b;
+        public static final int encryption_choice_none=0x7f0a0031;
+        public static final int encryption_choice_otr=0x7f0a0032;
+        public static final int encryption_choice_pgp=0x7f0a0033;
+        public static final int list=0x7f0a0025;
+        public static final int message_body=0x7f0a0029;
+        public static final int message_photo=0x7f0a0028;
+        public static final int message_time=0x7f0a002a;
+        public static final int messages_view=0x7f0a0021;
         public static final int new_conversation_search=0x7f0a0004;
+        public static final int new_fingerprint=0x7f0a0022;
+        public static final int otr_fingerprint=0x7f0a0023;
         public static final int progressBar1=0x7f0a0003;
-        public static final int selected_conversation=0x7f0a0021;
-        public static final int slidingpanelayout=0x7f0a001f;
-        public static final int textSendButton=0x7f0a001d;
-        public static final int textView1=0x7f0a0015;
+        public static final int selected_conversation=0x7f0a0026;
+        public static final int slidingpanelayout=0x7f0a0024;
+        public static final int textSendButton=0x7f0a0020;
+        public static final int textView1=0x7f0a0018;
         public static final int textView2=0x7f0a0001;
-        public static final int textinput=0x7f0a001c;
-        public static final int textsend=0x7f0a001b;
+        public static final int textinput=0x7f0a001f;
+        public static final int textsend=0x7f0a001e;
+        public static final int verify_otr_fingerprint=0x7f0a0016;
+        public static final int verify_otr_jid=0x7f0a0015;
+        public static final int verify_otr_yourprint=0x7f0a0017;
     }
     public static final class layout {
         public static final int account_row=0x7f030000;
@@ -98,18 +106,20 @@ public final class R {
         public static final int contact=0x7f030002;
         public static final int conversation_list_row=0x7f030003;
         public static final int dialog_contact_details=0x7f030004;
-        public static final int edit_account_dialog=0x7f030005;
-        public static final int fragment_conversation=0x7f030006;
-        public static final int fragment_conversations_overview=0x7f030007;
-        public static final int manage_accounts=0x7f030008;
-        public static final int message_recieved=0x7f030009;
-        public static final int message_sent=0x7f03000a;
+        public static final int dialog_verify_otr=0x7f030005;
+        public static final int edit_account_dialog=0x7f030006;
+        public static final int fragment_conversation=0x7f030007;
+        public static final int fragment_conversations_overview=0x7f030008;
+        public static final int manage_accounts=0x7f030009;
+        public static final int message_recieved=0x7f03000a;
+        public static final int message_sent=0x7f03000b;
     }
     public static final class menu {
         public static final int conversations=0x7f090000;
-        public static final int manageaccounts=0x7f090001;
-        public static final int manageaccounts_context=0x7f090002;
-        public static final int newconversation=0x7f090003;
+        public static final int encryption_choices=0x7f090001;
+        public static final int manageaccounts=0x7f090002;
+        public static final int manageaccounts_context=0x7f090003;
+        public static final int newconversation=0x7f090004;
     }
     public static final class string {
         public static final int action_accounts=0x7f070003;

res/layout/dialog_verify_otr.xml 🔗

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingLeft="8dp">
+    
+ <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:text="Jabber ID"
+        android:textColor="#33B5E5"
+        android:textSize="20sp"/>
+
+    <TextView
+        android:id="@+id/verify_otr_jid"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="8dp"
+        android:text="julia@jabber.example.com"
+        android:textSize="14sp" />
+     <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:text="OTR fingerprint"
+        android:textColor="#33B5E5"
+        android:textSize="20sp"/>
+
+    <TextView
+        android:id="@+id/verify_otr_fingerprint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="8dp"
+        android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
+        android:textSize="14sp" />
+     <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:text="Your fingerprint"
+        android:textColor="#33B5E5"
+        android:textSize="20sp"/>
+
+    <TextView
+        android:id="@+id/verify_otr_yourprint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="8dp"
+        android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
+        android:textSize="14sp" />
+</LinearLayout>

res/layout/fragment_conversation.xml 🔗

@@ -53,5 +53,35 @@
 		android:transcriptMode="alwaysScroll"
 		android:listSelector="@android:color/transparent">
     </ListView>
+    <LinearLayout
+        android:id="@+id/new_fingerprint"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignTop="@+id/messages_view"
+        android:background="#cc0000"
+        android:orientation="vertical"
+        android:visibility="gone"
+        >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Unknown OTR Fingerprint"
+            android:textColor="#eee"
+            android:textStyle="bold"
+            android:padding="8dp"
+            android:textSize="20sp"/>
+        <TextView 
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/otr_fingerprint"
+            android:textColor="#eee"
+            android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
+            android:paddingLeft="8dp"
+            android:paddingBottom="8dp"
+            android:textSize="14sp"/>
+        
+    </LinearLayout>
 
 </RelativeLayout>

res/menu/encryption_choices.xml 🔗

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+     <group android:checkableBehavior="single">
+    
+<item
+    android:id="@+id/encryption_choice_none"
+    android:title="Plain text"
+    />
+<item
+    android:id="@+id/encryption_choice_otr"
+    android:title="OTR"
+    />
+<item
+    android:id="@+id/encryption_choice_pgp"
+    android:title="OpenPGP"/>
+</group>
+</menu>

src/de/gultsch/chat/crypto/OtrEngine.java 🔗

@@ -124,6 +124,10 @@ public class OtrEngine implements OtrEngineHost {
 		return null;
 	}
 
+	public PublicKey getPublicKey() {
+		return this.keyPair.getPublic();
+	}
+	
 	@Override
 	public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
 		if (this.keyPair==null) {

src/de/gultsch/chat/entities/Account.java 🔗

@@ -1,5 +1,10 @@
 package de.gultsch.chat.entities;
 
+import java.security.interfaces.DSAPublicKey;
+
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.crypto.OtrCryptoException;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -48,6 +53,8 @@ public class Account  extends AbstractEntity{
 	
 	transient OtrEngine otrEngine = null;
 	transient XmppConnection xmppConnection = null;
+
+	private String otrFingerprint;
 	
 	public Account() {
 		this.uuid = "0";
@@ -177,4 +184,21 @@ public class Account  extends AbstractEntity{
 	public String getFullJid() {
 		return this.getJid()+"/"+this.resource;
 	}
+	
+	public String getOtrFingerprint() {
+		if (this.otrFingerprint == null) {
+			try {
+				DSAPublicKey pubkey = (DSAPublicKey) this.otrEngine.getPublicKey();
+				StringBuilder builder = new StringBuilder(new OtrCryptoEngineImpl().getFingerprint(pubkey));
+				builder.insert(8, " ");
+				builder.insert(17, " ");
+				builder.insert(26, " ");
+				builder.insert(35, " ");
+				this.otrFingerprint = builder.toString();
+			} catch (OtrCryptoException e) {
+				
+			}
+		}
+		return this.otrFingerprint;
+	}
 }

src/de/gultsch/chat/entities/Contact.java 🔗

@@ -1,7 +1,13 @@
 package de.gultsch.chat.entities;
 
 import java.io.Serializable;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Set;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -16,7 +22,7 @@ public class Contact extends AbstractEntity implements Serializable {
 	public static final String SUBSCRIPTION = "subscription";
 	public static final String SYSTEMACCOUNT = "systemaccount";
 	public static final String PHOTOURI = "photouri";
-	public static final String OPENPGPKEY = "pgpkey";
+	public static final String KEYS = "pgpkey";
 	public static final String PRESENCES = "presences";
 	public static final String ACCOUNT = "accountUuid";
 
@@ -26,7 +32,7 @@ public class Contact extends AbstractEntity implements Serializable {
 	protected String subscription;
 	protected String systemAccount;
 	protected String photoUri;
-	protected String openPGPKey;
+	protected JSONObject keys;
 	protected Presences presences = new Presences();
 
 	protected Account account;
@@ -45,7 +51,7 @@ public class Contact extends AbstractEntity implements Serializable {
 
 	public Contact(String uuid, String account, String displayName, String jid,
 			String subscription, String photoUri, String systemAccount,
-			String pgpKey,String presences) {
+			String keys, String presences) {
 		this.uuid = uuid;
 		this.accountUuid = account;
 		this.displayName = displayName;
@@ -53,7 +59,14 @@ public class Contact extends AbstractEntity implements Serializable {
 		this.subscription = subscription;
 		this.photoUri = photoUri;
 		this.systemAccount = systemAccount;
-		this.openPGPKey = pgpKey;
+		if (keys == null) {
+			keys = "";
+		}
+		try {
+			this.keys = new JSONObject(keys);
+		} catch (JSONException e) {
+			this.keys = new JSONObject();
+		}
 		this.presences = Presences.fromJsonString(presences);
 	}
 
@@ -84,7 +97,7 @@ public class Contact extends AbstractEntity implements Serializable {
 		values.put(SUBSCRIPTION, subscription);
 		values.put(SYSTEMACCOUNT, systemAccount);
 		values.put(PHOTOURI, photoUri);
-		values.put(OPENPGPKEY, openPGPKey);
+		values.put(KEYS, keys.toString());
 		values.put(PRESENCES, presences.toJsonString());
 		return values;
 	}
@@ -97,14 +110,14 @@ public class Contact extends AbstractEntity implements Serializable {
 				cursor.getString(cursor.getColumnIndex(SUBSCRIPTION)),
 				cursor.getString(cursor.getColumnIndex(PHOTOURI)),
 				cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
-				cursor.getString(cursor.getColumnIndex(OPENPGPKEY)),
+				cursor.getString(cursor.getColumnIndex(KEYS)),
 				cursor.getString(cursor.getColumnIndex(PRESENCES)));
 	}
 
 	public void setSubscription(String subscription) {
 		this.subscription = subscription;
 	}
-	
+
 	public String getSubscription() {
 		return this.subscription;
 	}
@@ -141,11 +154,11 @@ public class Contact extends AbstractEntity implements Serializable {
 			}
 		}
 	}
-	
+
 	public Hashtable<String, Integer> getPresences() {
 		return this.presences.getPresences();
 	}
-	
+
 	public void updatePresence(String resource, int status) {
 		this.presences.updatePresence(resource, status);
 	}
@@ -153,19 +166,19 @@ public class Contact extends AbstractEntity implements Serializable {
 	public void removePresence(String resource) {
 		this.presences.removePresence(resource);
 	}
-	
+
 	public int getMostAvailableStatus() {
 		return this.presences.getMostAvailableStatus();
 	}
 
 	public void setPresences(Presences pres) {
-		this.presences = pres;	
+		this.presences = pres;
 	}
-	
+
 	public void setPhotoUri(String uri) {
 		this.photoUri = uri;
 	}
-	
+
 	public void setDisplayName(String name) {
 		this.displayName = name;
 	}
@@ -173,4 +186,36 @@ public class Contact extends AbstractEntity implements Serializable {
 	public String getSystemAccount() {
 		return systemAccount;
 	}
+
+	public Set<String> getOtrFingerprints() {
+		Set<String> set = new HashSet<String>();
+		try {
+			if (this.keys.has("otr_fingerprints")) {
+				JSONArray fingerprints = this.keys.getJSONArray("otr_fingerprints");
+				for (int i = 0; i < fingerprints.length(); ++i) {
+					set.add(fingerprints.getString(i));
+				}
+			}
+		} catch (JSONException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return set;
+	}
+
+	public void addOtrFingerprint(String print) {
+		try {
+			JSONArray fingerprints;
+			if (!this.keys.has("otr_fingerprints")) {
+				fingerprints = new JSONArray();
+
+			} else {
+				fingerprints = this.keys.getJSONArray("otr_fingerprints");
+			}
+			fingerprints.put(print);
+			this.keys.put("otr_fingerprints", fingerprints);
+		} catch (JSONException e) {
+
+		}
+	}
 }

src/de/gultsch/chat/entities/Conversation.java 🔗

@@ -1,5 +1,6 @@
 package de.gultsch.chat.entities;
 
+import java.security.interfaces.DSAPublicKey;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -7,6 +8,8 @@ import de.gultsch.chat.crypto.OtrEngine;
 import de.gultsch.chat.xmpp.XmppConnection;
 
 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;
 import net.java.otr4j.session.SessionStatus;
@@ -51,7 +54,10 @@ public class Conversation extends AbstractEntity {
 	private transient Contact contact;
 	
 	private transient SessionImpl otrSession;
-	private transient String foreignOtrPresence;
+	
+	private transient String otrFingerprint = null;
+	
+	public int nextMessageEncryption = Message.ENCRYPTION_NONE;
 
 	public Conversation(String name, Account account,
 			String contactJid, int mode) {
@@ -209,6 +215,11 @@ public class Conversation extends AbstractEntity {
 		Log.d("xmppService","starting otr session with "+presence);
 		SessionID sessionId = new SessionID(this.getContactJid(),presence,"xmpp");
 		this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(context));
+		try {
+			this.otrSession.startSession();
+		} catch (OtrException e) {
+			Log.d("xmppServic","couldnt start otr");
+		}
 	}
 	
 	public SessionImpl getOtrSession() {
@@ -225,9 +236,36 @@ public class Conversation extends AbstractEntity {
 				this.otrSession.endSession();
 			}
 		}
+		this.resetOtrSession();
 	}
 
-	public boolean hasOtrSession() {
-		return (this.otrSession!=null);
+	public boolean hasValidOtrSession() {
+		if (this.otrSession == null) {
+			return false;
+		} else {
+			String foreignPresence = this.otrSession.getSessionID().getUserID();
+			if (!getContact().getPresences().containsKey(foreignPresence)) {
+				this.resetOtrSession();
+				return false;
+			}
+			return true;
+		}
+	}
+	
+	public String getOtrFingerprint() {
+		if (this.otrFingerprint == null) {
+			try {
+				DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
+				StringBuilder builder = new StringBuilder(new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
+				builder.insert(8, " ");
+				builder.insert(17, " ");
+				builder.insert(26, " ");
+				builder.insert(35, " ");
+				this.otrFingerprint = builder.toString();
+			} catch (OtrCryptoException e) {
+				
+			}
+		}
+		return this.otrFingerprint;
 	}
 }

src/de/gultsch/chat/entities/Message.java 🔗

@@ -131,5 +131,9 @@ public class Message extends AbstractEntity {
 	public void setTime(long time) {
 		this.timeSent = time;
 	}
+
+	public void setEncryption(int encryption) {
+		this.encryption = encryption;
+	}
 	
 }

src/de/gultsch/chat/persistance/DatabaseBackend.java 🔗

@@ -56,7 +56,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		db.execSQL("create table " + Contact.TABLENAME + "(" + Contact.UUID
 				+ " TEXT PRIMARY KEY, " + Contact.ACCOUNT + " TEXT, "
 				+ Contact.DISPLAYNAME + " TEXT," + Contact.JID + " TEXT,"
-				+ Contact.PRESENCES + " TEXT, " + Contact.OPENPGPKEY
+				+ Contact.PRESENCES + " TEXT, " + Contact.KEYS
 				+ " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.SUBSCRIPTION
 				+ " TEXT," + Contact.SYSTEMACCOUNT + " NUMBER, "
 				+ "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
@@ -227,10 +227,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			}
 		}
 	}
-	
-	public void mergeMessageIntoConversation(Message message) {
-		// select counterpart,body,(timeSent/1000)-180 as min,(timeSent/1000)+180 as max from messages where min<1392035670 and max>1392035670;
-	}
 
 	public List<Contact> getContacts(Account account) {
 		List<Contact> list = new ArrayList<Contact>();

src/de/gultsch/chat/services/XmppConnectionService.java 🔗

@@ -132,7 +132,7 @@ public class XmppConnectionService extends Service {
 				} else {
 					counterPart = fullJid;
 					if ((runOtrCheck) && body.startsWith("?OTR")) {
-						if (!conversation.hasOtrSession()) {
+						if (!conversation.hasValidOtrSession()) {
 							conversation.startOtrSession(
 									getApplicationContext(), fromParts[1]);
 						}
@@ -162,6 +162,9 @@ public class XmppConnectionService extends Service {
 								if (convChangedListener!=null) {
 									convChangedListener.onConversationListChanged();
 								}
+							} else if ((before != after) && (after == SessionStatus.FINISHED)) {
+								conversation.resetOtrSession();
+								Log.d(LOGTAG,"otr session stoped");
 							}
 						} catch (Exception e) {
 							Log.d(LOGTAG, "error receiving otr. resetting");
@@ -326,61 +329,18 @@ public class XmppConnectionService extends Service {
 		thread.start();
 		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) {
+	public void sendMessage(Account account, Message message, String presence) {
 		Conversation conv = message.getConversation();
 		boolean saveInDb = false;
 		boolean addToConversation = false;
 		if (account.getStatus() == Account.STATUS_ONLINE) {
 			MessagePacket packet;
 			if (message.getEncryption() == Message.ENCRYPTION_OTR) {
-				if (!conv.hasOtrSession()) {
+				if (!conv.hasValidOtrSession()) {
 					//starting otr session. messages will be send later
-					startOtrSession(conv);
-				} else {
+					conv.startOtrSession(getApplicationContext(), presence);
+				} else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED){
 					//otr session aleary exists, creating message packet accordingly
 					packet = prepareMessagePacket(account, message,
 							conv.getOtrSession());
@@ -646,6 +606,7 @@ public class XmppConnectionService extends Service {
 				conversation.setMode(Conversation.MODE_SINGLE);
 			}
 			this.databaseBackend.updateConversation(conversation);
+			conversation.setContact(findContact(account, conversation.getContactJid()));
 		} else {
 			String conversationName;
 			Contact contact = findContact(account, jid);
@@ -766,9 +727,9 @@ public class XmppConnectionService extends Service {
 		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
 		if (conversation.getMessages().size() != 0) {
 			Element history = new Element("history");
-			history.setAttribute("seconds",
-					(System.currentTimeMillis() - conversation
-							.getLatestMessage().getTimeSent() / 1000) + "");
+			long lastMsgTime = conversation.getLatestMessage().getTimeSent();
+			long diff = (System.currentTimeMillis() - lastMsgTime) / 1000;
+			history.setAttribute("seconds",diff+"");
 			x.addChild(history);
 		}
 		packet.addChild(x);
@@ -806,4 +767,8 @@ public class XmppConnectionService extends Service {
 	public IBinder onBind(Intent intent) {
 		return mBinder;
 	}
+
+	public void updateContact(Contact contact) {
+		databaseBackend.updateContact(contact);
+	}
 }

src/de/gultsch/chat/ui/ConversationActivity.java 🔗

@@ -32,6 +32,8 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
 import android.widget.TextView;
 import android.widget.ImageView;
 
@@ -295,6 +297,56 @@ public class ConversationActivity extends XmppActivity {
 			} else {
 				Log.d("xmppService","contact was null - means not in roster");
 			}
+			break;
+		case R.id.action_security:
+			final Conversation selConv = getSelectedConversation();
+			View menuItemView = findViewById(R.id.action_security);
+			PopupMenu popup = new PopupMenu(this, menuItemView);
+			final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
+		    if (fragment!=null) {
+				popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+					
+					@Override
+					public boolean onMenuItemClick(MenuItem item) {
+						switch (item.getItemId()) {
+						case R.id.encryption_choice_none:
+							selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
+							item.setChecked(true);
+							break;
+						case R.id.encryption_choice_otr:
+							selConv.nextMessageEncryption = Message.ENCRYPTION_OTR;
+							item.setChecked(true);
+							break;
+						case R.id.encryption_choice_pgp:
+							selConv.nextMessageEncryption = Message.ENCRYPTION_PGP;
+							item.setChecked(true);
+							break;
+						default:
+							selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
+							break;
+						}
+						fragment.updateChatMsgHint();
+						return true;
+					}
+				});
+			    popup.inflate(R.menu.encryption_choices);
+			    switch (selConv.nextMessageEncryption) {
+				case Message.ENCRYPTION_NONE:
+					popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
+					break;
+				case Message.ENCRYPTION_OTR:
+					popup.getMenu().findItem(R.id.encryption_choice_otr).setChecked(true);
+					break;
+				case Message.ENCRYPTION_PGP:
+					popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
+					break;
+				default:
+					popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
+					break;
+				}
+			    popup.show();
+		    }
+
 			break;
 		default:
 			break;
@@ -344,13 +396,6 @@ public class ConversationActivity extends XmppActivity {
 	@Override
 	void onBackendConnected() {
 		
-		
-		if (contactInserted) {
-			Log.d("xmppService","merge phone contacts with roster");
-			contactInserted = false;
-			xmppConnectionService.mergePhoneContactsWithRoster();
-		}
-		
 		xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged);
 		
 		if (conversationList.size()==0) {

src/de/gultsch/chat/ui/ConversationFragment.java 🔗

@@ -1,15 +1,23 @@
 package de.gultsch.chat.ui;
 
 import java.util.ArrayList;
+import java.util.Hashtable;
 import java.util.List;
+import java.util.Set;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.session.SessionStatus;
 
 import de.gultsch.chat.R;
 import de.gultsch.chat.entities.Contact;
 import de.gultsch.chat.entities.Conversation;
 import de.gultsch.chat.entities.Message;
+import de.gultsch.chat.services.XmppConnectionService;
 import de.gultsch.chat.utils.PhoneHelper;
 import de.gultsch.chat.utils.UIHelper;
+import android.app.AlertDialog;
 import android.app.Fragment;
+import android.content.DialogInterface;
 import android.content.SharedPreferences;
 import android.graphics.Typeface;
 import android.net.Uri;
@@ -21,23 +29,97 @@ import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.EditText;
+import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 public class ConversationFragment extends Fragment {
-	
+
 	protected Conversation conversation;
 	protected ListView messagesView;
 	protected LayoutInflater inflater;
 	protected List<Message> messageList = new ArrayList<Message>();
 	protected ArrayAdapter<Message> messageListAdapter;
 	protected Contact contact;
-	
+
 	private EditText chatMsg;
-	private int nextMessageEncryption = Message.ENCRYPTION_NONE;
-	
+
+	private OnClickListener sendMsgListener = new OnClickListener() {
+
+		@Override
+		public void onClick(View v) {
+			ConversationActivity activity = (ConversationActivity) getActivity();
+			final XmppConnectionService xmppService = activity.xmppConnectionService;
+			if (chatMsg.getText().length() < 1)
+				return;
+			final Message message = new Message(conversation, chatMsg.getText()
+					.toString(), conversation.nextMessageEncryption);
+			if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
+				if (conversation.hasValidOtrSession()) {
+					activity.xmppConnectionService.sendMessage(
+							conversation.getAccount(), message, null);
+					chatMsg.setText("");
+				} else {
+					Hashtable<String, Integer> presences = conversation
+							.getContact().getPresences();
+					if (presences.size() == 0) {
+						AlertDialog.Builder builder = new AlertDialog.Builder(
+								getActivity());
+						builder.setTitle("Contact is offline");
+						builder.setIconAttribute(android.R.attr.alertDialogIcon);
+						builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
+						builder.setPositiveButton("Send plain text",
+								new DialogInterface.OnClickListener() {
+
+									@Override
+									public void onClick(DialogInterface dialog,
+											int which) {
+										conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
+										message.setEncryption(Message.ENCRYPTION_NONE);
+										xmppService.sendMessage(
+												conversation.getAccount(),
+												message, null);
+										chatMsg.setText("");
+									}
+								});
+						builder.setNegativeButton("Cancel", null);
+						builder.create().show();
+					} else if (presences.size() == 1) {
+						xmppService.sendMessage(conversation.getAccount(),
+								message,
+								(String) presences.keySet().toArray()[0]);
+						chatMsg.setText("");
+					}
+				}
+			} else {
+				xmppService.sendMessage(conversation.getAccount(), message,
+						null);
+				chatMsg.setText("");
+			}
+		}
+	};
+
+	public void updateChatMsgHint() {
+		if (conversation.getMode() == Conversation.MODE_MULTI) {
+			chatMsg.setHint("Send message to conference");
+		} else {
+			switch (conversation.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;
+			}
+		}
+	}
+
 	@Override
 	public View onCreateView(final LayoutInflater inflater,
 			ViewGroup container, Bundle savedInstanceState) {
@@ -47,40 +129,32 @@ public class ConversationFragment extends Fragment {
 		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();
-						if (chatMsg.getText().length() < 1)
-							return;
-						Message message = new Message(conversation, chatMsg
-								.getText().toString(), nextMessageEncryption);
-						activity.xmppConnectionService.sendMessage(conversation.getAccount(),message);
-						chatMsg.setText("");
-					}
-				});
+		ImageButton sendButton = (ImageButton) view
+				.findViewById(R.id.textSendButton);
+		sendButton.setOnClickListener(this.sendMsgListener);
 
 		messagesView = (ListView) view.findViewById(R.id.messages_view);
-		
+
 		SharedPreferences sharedPref = PreferenceManager
-				.getDefaultSharedPreferences(getActivity().getApplicationContext());
-		boolean showPhoneSelfContactPicture = sharedPref.getBoolean("show_phone_selfcontact_picture",true);
-		
+				.getDefaultSharedPreferences(getActivity()
+						.getApplicationContext());
+		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
+				"show_phone_selfcontact_picture", true);
+
 		final Uri selfiUri;
 		if (showPhoneSelfContactPicture) {
-			selfiUri =  PhoneHelper.getSefliUri(getActivity());
+			selfiUri = PhoneHelper.getSefliUri(getActivity());
 		} else {
 			selfiUri = null;
 		}
-		
+
 		messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
-				.getApplicationContext(), R.layout.message_sent, this.messageList) {
+				.getApplicationContext(), R.layout.message_sent,
+				this.messageList) {
 
 			private static final int SENT = 0;
 			private static final int RECIEVED = 1;
-			
+
 			@Override
 			public int getViewTypeCount() {
 				return 2;
@@ -111,32 +185,42 @@ public class ConversationFragment extends Fragment {
 						break;
 					}
 				}
-				ImageView imageView = (ImageView) view.findViewById(R.id.message_photo);
+				ImageView imageView = (ImageView) view
+						.findViewById(R.id.message_photo);
 				if (type == RECIEVED) {
-					if(item.getConversation().getMode()==Conversation.MODE_SINGLE) {
+					if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
 						Uri uri = item.getConversation().getProfilePhotoUri();
-						if (uri!=null) {
+						if (uri != null) {
 							imageView.setImageURI(uri);
 						} else {
-							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200));
+							imageView.setImageBitmap(UIHelper
+									.getUnknownContactPicture(item
+											.getConversation().getName(), 200));
 						}
-					} else if (item.getConversation().getMode()==Conversation.MODE_MULTI) {
-						if (item.getCounterpart()!=null) {
-							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getCounterpart(), 200));
+					} else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
+						if (item.getCounterpart() != null) {
+							imageView.setImageBitmap(UIHelper
+									.getUnknownContactPicture(
+											item.getCounterpart(), 200));
 						} else {
-							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200));
+							imageView.setImageBitmap(UIHelper
+									.getUnknownContactPicture(item
+											.getConversation().getName(), 200));
 						}
 					}
 				} else {
-					if (selfiUri!=null) {
+					if (selfiUri != null) {
 						imageView.setImageURI(selfiUri);
 					} else {
-						imageView.setImageBitmap(UIHelper.getUnknownContactPicture(conversation.getAccount().getJid(),200));
+						imageView.setImageBitmap(UIHelper
+								.getUnknownContactPicture(conversation
+										.getAccount().getJid(), 200));
 					}
 				}
-				TextView messageBody = (TextView) view.findViewById(R.id.message_body);
+				TextView messageBody = (TextView) view
+						.findViewById(R.id.message_body);
 				String body = item.getBody();
-				if (body!=null) {
+				if (body != null) {
 					messageBody.setText(body.trim());
 				}
 				TextView time = (TextView) view.findViewById(R.id.message_time);
@@ -144,13 +228,16 @@ public class ConversationFragment extends Fragment {
 					time.setTypeface(null, Typeface.ITALIC);
 					time.setText("sending\u2026");
 				} else {
-					time.setTypeface(null,Typeface.NORMAL);
-					if ((item.getConversation().getMode()==Conversation.MODE_SINGLE)||(type != RECIEVED)) {
+					time.setTypeface(null, Typeface.NORMAL);
+					if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
+							|| (type != RECIEVED)) {
 						time.setText(UIHelper.readableTimeDifference(item
-							.getTimeSent()));
-					} else {
-						time.setText(item.getCounterpart()+" \u00B7 "+UIHelper.readableTimeDifference(item
 								.getTimeSent()));
+					} else {
+						time.setText(item.getCounterpart()
+								+ " \u00B7 "
+								+ UIHelper.readableTimeDifference(item
+										.getTimeSent()));
 					}
 				}
 				return view;
@@ -165,7 +252,7 @@ public class ConversationFragment extends Fragment {
 	public void onStart() {
 		super.onStart();
 		final ConversationActivity activity = (ConversationActivity) getActivity();
-		
+
 		if (activity.xmppConnectionServiceBound) {
 			this.conversation = activity.getSelectedConversation();
 			updateMessages();
@@ -182,7 +269,7 @@ public class ConversationFragment extends Fragment {
 			}
 		}
 	}
-	
+
 	public void onBackendConnected() {
 		final ConversationActivity activity = (ConversationActivity) getActivity();
 		this.conversation = activity.getSelectedConversation();
@@ -201,32 +288,50 @@ public class ConversationFragment extends Fragment {
 	}
 
 	public void updateMessages() {
+		ConversationActivity activity = (ConversationActivity) getActivity();
 		this.messageList.clear();
 		this.messageList.addAll(this.conversation.getMessages());
 		this.messageListAdapter.notifyDataSetChanged();
-		if (messageList.size()>=1) {
-			nextMessageEncryption = this.conversation.getLatestMessage().getEncryption();
+		if (messageList.size() >= 1) {
+			int latestEncryption = this.conversation.getLatestMessage()
+					.getEncryption();
+			conversation.nextMessageEncryption = latestEncryption;
+			makeFingerprintWarning(latestEncryption);
 		}
 		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;
-		}
+		updateChatMsgHint();
 		int size = this.messageList.size();
 		if (size >= 1)
 			messagesView.setSelection(size - 1);
-		ConversationActivity activity = (ConversationActivity) getActivity();
 		if (!activity.shouldPaneBeOpen()) {
 			conversation.markRead();
 			activity.updateConversationList();
 		}
 	}
+
+	protected void makeFingerprintWarning(int latestEncryption) {
+		final LinearLayout fingerprintWarning = (LinearLayout) getView()
+				.findViewById(R.id.new_fingerprint);
+		Set<String> knownFingerprints = conversation.getContact()
+				.getOtrFingerprints();
+		if ((latestEncryption == Message.ENCRYPTION_OTR)
+				&& (conversation.hasValidOtrSession()
+						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
+							.contains(conversation.getOtrFingerprint())))) {
+			fingerprintWarning.setVisibility(View.VISIBLE);
+			TextView fingerprint = (TextView) getView().findViewById(
+					R.id.otr_fingerprint);
+			fingerprint.setText(conversation.getOtrFingerprint());
+			fingerprintWarning.setOnClickListener(new OnClickListener() {
+
+				@Override
+				public void onClick(View v) {
+					AlertDialog dialog = UIHelper.getVerifyFingerprintDialog((ConversationActivity) getActivity(),conversation,fingerprintWarning);
+					dialog.show();
+				}
+			});
+		} else {
+			fingerprintWarning.setVisibility(View.GONE);
+		}
+	}
 }

src/de/gultsch/chat/utils/UIHelper.java 🔗

@@ -7,15 +7,19 @@ import java.util.Date;
 import java.util.List;
 
 import de.gultsch.chat.R;
+import de.gultsch.chat.entities.Account;
 import de.gultsch.chat.entities.Contact;
 import de.gultsch.chat.entities.Conversation;
 import de.gultsch.chat.entities.Message;
 import de.gultsch.chat.ui.ConversationActivity;
 
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
@@ -29,7 +33,11 @@ import android.provider.ContactsContract.Contacts;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.TaskStackBuilder;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
 import android.widget.QuickContactBadge;
+import android.widget.TextView;
 
 public class UIHelper {
 	public static String readableTimeDifference(long time) {
@@ -153,4 +161,33 @@ public class UIHelper {
 		}
 
 	}
+	
+	public static AlertDialog getVerifyFingerprintDialog(final ConversationActivity activity,final Conversation conversation, final LinearLayout msg) {
+		final Contact contact = conversation.getContact();
+		final Account account = conversation.getAccount();
+		
+		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+		builder.setTitle("Verify fingerprint");
+		LayoutInflater inflater = activity.getLayoutInflater();
+		View view = inflater.inflate(R.layout.dialog_verify_otr, null);
+		TextView jid = (TextView) view.findViewById(R.id.verify_otr_jid);
+		TextView fingerprint = (TextView) view.findViewById(R.id.verify_otr_fingerprint);
+		TextView yourprint = (TextView) view.findViewById(R.id.verify_otr_yourprint);
+		
+		jid.setText(contact.getJid());
+		fingerprint.setText(conversation.getOtrFingerprint());
+		yourprint.setText(account.getOtrFingerprint());
+		builder.setNegativeButton("Cancel", null);
+		builder.setPositiveButton("Verify", new OnClickListener() {
+			
+			@Override
+			public void onClick(DialogInterface dialog, int which) {
+				contact.addOtrFingerprint(conversation.getOtrFingerprint());
+				msg.setVisibility(View.GONE);
+				activity.xmppConnectionService.updateContact(contact);
+			}
+		});
+		builder.setView(view);
+		return builder.create();
+	}
 }