From 9c7cacdbddf6ba1a34fd56b71718ba4e44e20efa Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 16 Feb 2014 16:32:15 +0100 Subject: [PATCH] show unknown otr fingerprint warining --- 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 +++++- .../gultsch/chat/entities/Conversation.java | 44 +++- src/de/gultsch/chat/entities/Message.java | 4 + .../chat/persistance/DatabaseBackend.java | 6 +- .../chat/services/XmppConnectionService.java | 67 ++---- .../gultsch/chat/ui/ConversationActivity.java | 59 ++++- .../gultsch/chat/ui/ConversationFragment.java | 225 +++++++++++++----- src/de/gultsch/chat/utils/UIHelper.java | 37 +++ 14 files changed, 549 insertions(+), 176 deletions(-) create mode 100644 res/layout/dialog_verify_otr.xml create mode 100644 res/menu/encryption_choices.xml diff --git a/gen/de/gultsch/chat/R.java b/gen/de/gultsch/chat/R.java index 6f1b50dee..857311176 100644 --- a/gen/de/gultsch/chat/R.java +++ b/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; diff --git a/res/layout/dialog_verify_otr.xml b/res/layout/dialog_verify_otr.xml new file mode 100644 index 000000000..9f389c73a --- /dev/null +++ b/res/layout/dialog_verify_otr.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + diff --git a/res/layout/fragment_conversation.xml b/res/layout/fragment_conversation.xml index 3b965af28..5db687481 100644 --- a/res/layout/fragment_conversation.xml +++ b/res/layout/fragment_conversation.xml @@ -53,5 +53,35 @@ android:transcriptMode="alwaysScroll" android:listSelector="@android:color/transparent"> + + + + + + \ No newline at end of file diff --git a/res/menu/encryption_choices.xml b/res/menu/encryption_choices.xml new file mode 100644 index 000000000..ade176548 --- /dev/null +++ b/res/menu/encryption_choices.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/de/gultsch/chat/crypto/OtrEngine.java b/src/de/gultsch/chat/crypto/OtrEngine.java index d994f0f9a..f63b2904b 100644 --- a/src/de/gultsch/chat/crypto/OtrEngine.java +++ b/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) { diff --git a/src/de/gultsch/chat/entities/Account.java b/src/de/gultsch/chat/entities/Account.java index 7f14b0901..1bced45fc 100644 --- a/src/de/gultsch/chat/entities/Account.java +++ b/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; + } } diff --git a/src/de/gultsch/chat/entities/Contact.java b/src/de/gultsch/chat/entities/Contact.java index 9c987e090..5d5710a5c 100644 --- a/src/de/gultsch/chat/entities/Contact.java +++ b/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 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 getOtrFingerprints() { + Set set = new HashSet(); + 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) { + + } + } } diff --git a/src/de/gultsch/chat/entities/Conversation.java b/src/de/gultsch/chat/entities/Conversation.java index 7339aadb3..343e83a44 100644 --- a/src/de/gultsch/chat/entities/Conversation.java +++ b/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; } } diff --git a/src/de/gultsch/chat/entities/Message.java b/src/de/gultsch/chat/entities/Message.java index a168c645a..73a6e46cc 100644 --- a/src/de/gultsch/chat/entities/Message.java +++ b/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; + } } diff --git a/src/de/gultsch/chat/persistance/DatabaseBackend.java b/src/de/gultsch/chat/persistance/DatabaseBackend.java index df919f728..4607883c3 100644 --- a/src/de/gultsch/chat/persistance/DatabaseBackend.java +++ b/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 getContacts(Account account) { List list = new ArrayList(); diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java index b1c4eca50..63b894abc 100644 --- a/src/de/gultsch/chat/services/XmppConnectionService.java +++ b/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 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 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); + } } \ No newline at end of file diff --git a/src/de/gultsch/chat/ui/ConversationActivity.java b/src/de/gultsch/chat/ui/ConversationActivity.java index 011261e7a..b1464031b 100644 --- a/src/de/gultsch/chat/ui/ConversationActivity.java +++ b/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) { diff --git a/src/de/gultsch/chat/ui/ConversationFragment.java b/src/de/gultsch/chat/ui/ConversationFragment.java index 8ec511fc2..1f125ab07 100644 --- a/src/de/gultsch/chat/ui/ConversationFragment.java +++ b/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 messageList = new ArrayList(); protected ArrayAdapter 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 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(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 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); + } + } } diff --git a/src/de/gultsch/chat/utils/UIHelper.java b/src/de/gultsch/chat/utils/UIHelper.java index df3f9c1cc..52292a0f4 100644 --- a/src/de/gultsch/chat/utils/UIHelper.java +++ b/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(); + } }