Merge branch 'feature/otr_verification' into development

iNPUTmice created

Change summary

src/main/AndroidManifest.xml                                      |   4 
src/main/java/eu/siacs/conversations/crypto/OtrEngine.java        |  72 
src/main/java/eu/siacs/conversations/entities/Conversation.java   |  34 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 129 
src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java    | 296 +
src/main/java/eu/siacs/conversations/ui/XmppActivity.java         |   2 
src/main/java/eu/siacs/conversations/utils/UIHelper.java          |  34 
src/main/res/layout/activity_verify_otr.xml                       | 189 
src/main/res/layout/dialog_verify_otr.xml                         |  60 
src/main/res/values/strings.xml                                   |  17 
10 files changed, 655 insertions(+), 182 deletions(-)

Detailed changes

src/main/AndroidManifest.xml 🔗

@@ -95,6 +95,10 @@
             android:name=".ui.PublishProfilePictureActivity"
             android:label="@string/mgmt_account_publish_avatar"
             android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".ui.VerifyOTRActivity"
+            android:label="@string/verify_otr"
+            android:windowSoftInputMode="stateHidden" />
         <activity
             android:name=".ui.ShareWithActivity"
             android:label="@string/title_activity_conversations" >

src/main/java/eu/siacs/conversations/crypto/OtrEngine.java 🔗

@@ -18,13 +18,18 @@ import android.util.Log;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
 import eu.siacs.conversations.xmpp.stanzas.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.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.crypto.OtrCryptoException;
 import net.java.otr4j.session.InstanceTag;
 import net.java.otr4j.session.SessionID;
 
@@ -92,9 +97,18 @@ public class OtrEngine implements OtrEngineHost {
     }
 
 	@Override
-	public void askForSecret(SessionID arg0, InstanceTag arg1, String arg2) {
-		// TODO Auto-generated method stub
-
+	public void askForSecret(SessionID id, InstanceTag instanceTag, String question) {
+		try {
+			final Jid jid = Jid.fromSessionID(id);
+			Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
+			if (conversation!=null) {
+				conversation.smp().hint = question;
+				conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED;
+				mXmppConnectionService.updateConversationUi();
+			}
+		} catch (InvalidJidException e) {
+			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
+		}
 	}
 
 	@Override
@@ -110,8 +124,11 @@ public class OtrEngine implements OtrEngineHost {
 
 	@Override
 	public byte[] getLocalFingerprintRaw(SessionID arg0) {
-		// TODO Auto-generated method stub
-		return null;
+		try {
+			return new OtrCryptoEngineImpl().getFingerprintRaw(getPublicKey());
+		} catch (OtrCryptoException e) {
+			return null;
+		}
 	}
 
 	public PublicKey getPublicKey() {
@@ -187,20 +204,31 @@ public class OtrEngine implements OtrEngineHost {
 
 	@Override
 	public void showError(SessionID arg0, String arg1) throws OtrException {
-		// TODO Auto-generated method stub
-
+		Log.d(Config.LOGTAG,"show error");
 	}
 
 	@Override
-	public void smpAborted(SessionID arg0) throws OtrException {
-		// TODO Auto-generated method stub
+	public void smpAborted(SessionID id) throws OtrException {
+		setSmpStatus(id, Conversation.Smp.STATUS_NONE);
+	}
+
+	private void setSmpStatus(SessionID id, int status) {
+		try {
+			final Jid jid = Jid.fromSessionID(id);
+			Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
+			if (conversation!=null) {
+				conversation.smp().status = status;
+				mXmppConnectionService.updateConversationUi();
+			}
+		} catch (final InvalidJidException ignored) {
 
+		}
 	}
 
 	@Override
-	public void smpError(SessionID arg0, int arg1, boolean arg2)
+	public void smpError(SessionID id, int arg1, boolean arg2)
 			throws OtrException {
-		throw new OtrException(new Exception("smp error"));
+		setSmpStatus(id, Conversation.Smp.STATUS_NONE);
 	}
 
 	@Override
@@ -211,19 +239,29 @@ public class OtrEngine implements OtrEngineHost {
 
 	@Override
 	public void unreadableMessageReceived(SessionID arg0) throws OtrException {
+		Log.d(Config.LOGTAG,"unreadable message received");
 		throw new OtrException(new Exception("unreadable message received"));
 	}
 
 	@Override
-	public void unverify(SessionID arg0, String arg1) {
-		// TODO Auto-generated method stub
-
+	public void unverify(SessionID id, String arg1) {
+		setSmpStatus(id, Conversation.Smp.STATUS_FAILED);
 	}
 
 	@Override
-	public void verify(SessionID arg0, String arg1, boolean arg2) {
-		// TODO Auto-generated method stub
-
+	public void verify(SessionID id, String arg1, boolean arg2) {
+		try {
+			final Jid jid = Jid.fromSessionID(id);
+			Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
+			if (conversation!=null) {
+				conversation.smp().hint = null;
+				conversation.smp().status = Conversation.Smp.STATUS_VERIFIED;
+				conversation.verifyOtrFingerprint();
+				mXmppConnectionService.updateConversationUi();
+				mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
+			}
+		} catch (final InvalidJidException ignored) {
+		}
 	}
 
 }

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -63,13 +63,12 @@ public class Conversation extends AbstractEntity {
 	private transient SessionImpl otrSession;
 
 	private transient String otrFingerprint = null;
+	private Smp mSmp = new Smp();
 
 	private String nextMessage;
 
 	private transient MucOptions mucOptions = null;
 
-	// private transient String latestMarkableMessageId;
-
 	private byte[] symmetricKey;
 
 	private Bookmark bookmark;
@@ -271,6 +270,13 @@ public class Conversation extends AbstractEntity {
 	public void resetOtrSession() {
 		this.otrFingerprint = null;
 		this.otrSession = null;
+		this.mSmp.hint = null;
+		this.mSmp.secret = null;
+		this.mSmp.status = Smp.STATUS_NONE;
+	}
+
+	public Smp smp() {
+		return mSmp;
 	}
 
 	public void startOtrIfNeeded() {
@@ -330,6 +336,14 @@ public class Conversation extends AbstractEntity {
 		return this.otrFingerprint;
 	}
 
+	public void verifyOtrFingerprint() {
+		getContact().addOtrFingerprint(getOtrFingerprint());
+	}
+
+	public boolean isOtrFingerprintVerified() {
+		return getContact().getOtrFingerprints().contains(getOtrFingerprint());
+	}
+
 	public synchronized MucOptions getMucOptions() {
 		if (this.mucOptions == null) {
 			this.mucOptions = new MucOptions(this);
@@ -403,6 +417,10 @@ public class Conversation extends AbstractEntity {
 		}
 	}
 
+	public boolean smpRequested() {
+		return smp().status == Smp.STATUS_CONTACT_REQUESTED;
+	}
+
 	public void setNextMessage(String message) {
 		this.nextMessage = message;
 	}
@@ -503,4 +521,16 @@ public class Conversation extends AbstractEntity {
 			this.messages.addAll(index, messages);
 		}
 	}
+
+	public class Smp {
+		public static final int STATUS_NONE = 0;
+		public static final int STATUS_CONTACT_REQUESTED = 1;
+		public static final int STATUS_WE_REQUESTED = 2;
+		public static final int STATUS_FAILED = 3;
+		public static final int STATUS_VERIFIED = 4;
+
+		public String secret = null;
+		public String hint = null;
+		public int status = 0;
+	}
 }

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -35,7 +35,6 @@ import net.java.otr4j.session.SessionStatus;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import eu.siacs.conversations.R;
@@ -53,7 +52,6 @@ import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
 import eu.siacs.conversations.ui.adapter.MessageAdapter;
 import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
 import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
-import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
 public class ConversationFragment extends Fragment {
@@ -146,6 +144,19 @@ public class ConversationFragment extends Fragment {
 			}
 		}
 	};
+	protected OnClickListener clickToVerify = new OnClickListener() {
+
+		@Override
+		public void onClick(View v) {
+			if (conversation.getOtrFingerprint() != null) {
+				Intent intent = new Intent(getActivity(), VerifyOTRActivity.class);
+				intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
+				intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
+				intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString());
+				startActivity(intent);
+			}
+		}
+	};
 	private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>();
 	private boolean mDecryptJobRunning = false;
 	private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
@@ -296,7 +307,7 @@ public class ConversationFragment extends Fragment {
 											.getConversation());
 								}
 							}
-						}  else {
+						} else {
 							Account account = message.getConversation().getAccount();
 							Intent intent = new Intent(activity, EditAccountActivity.class);
 							intent.putExtra("jid", account.getJid().toBareJid().toString());
@@ -505,6 +516,39 @@ public class ConversationFragment extends Fragment {
 								activity.switchToContactDetails(contact);
 							}
 						});
+			} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
+				makeFingerprintWarning();
+			} else if (!conversation.getMucOptions().online()
+					&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
+				int error = conversation.getMucOptions().getError();
+				switch (error) {
+					case MucOptions.ERROR_NICK_IN_USE:
+						showSnackbar(R.string.nick_in_use, R.string.edit,
+								clickToMuc);
+						break;
+					case MucOptions.ERROR_ROOM_NOT_FOUND:
+						showSnackbar(R.string.conference_not_found,
+								R.string.leave, leaveMuc);
+						break;
+					case MucOptions.ERROR_PASSWORD_REQUIRED:
+						showSnackbar(R.string.conference_requires_password,
+								R.string.enter_password, enterPassword);
+						break;
+					case MucOptions.ERROR_BANNED:
+						showSnackbar(R.string.conference_banned,
+								R.string.leave, leaveMuc);
+						break;
+					case MucOptions.ERROR_MEMBERS_ONLY:
+						showSnackbar(R.string.conference_members_only,
+								R.string.leave, leaveMuc);
+						break;
+					case MucOptions.KICKED_FROM_ROOM:
+						showSnackbar(R.string.conference_kicked, R.string.join,
+								joinMuc);
+						break;
+					default:
+						break;
+				}
 			}
 			for (Message message : this.conversation.getMessages()) {
 				if (message.getEncryption() == Message.ENCRYPTION_PGP
@@ -526,44 +570,6 @@ public class ConversationFragment extends Fragment {
 				updateStatusMessages();
 			}
 			this.messageListAdapter.notifyDataSetChanged();
-			if (conversation.getMode() == Conversation.MODE_SINGLE) {
-				if (messageList.size() >= 1) {
-					makeFingerprintWarning();
-				}
-			} else {
-				if (!conversation.getMucOptions().online()
-						&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
-					int error = conversation.getMucOptions().getError();
-					switch (error) {
-						case MucOptions.ERROR_NICK_IN_USE:
-							showSnackbar(R.string.nick_in_use, R.string.edit,
-									clickToMuc);
-							break;
-						case MucOptions.ERROR_ROOM_NOT_FOUND:
-							showSnackbar(R.string.conference_not_found,
-									R.string.leave, leaveMuc);
-							break;
-						case MucOptions.ERROR_PASSWORD_REQUIRED:
-							showSnackbar(R.string.conference_requires_password,
-									R.string.enter_password, enterPassword);
-							break;
-						case MucOptions.ERROR_BANNED:
-							showSnackbar(R.string.conference_banned,
-									R.string.leave, leaveMuc);
-							break;
-						case MucOptions.ERROR_MEMBERS_ONLY:
-							showSnackbar(R.string.conference_members_only,
-									R.string.leave, leaveMuc);
-							break;
-						case MucOptions.KICKED_FROM_ROOM:
-							showSnackbar(R.string.conference_kicked, R.string.join,
-									joinMuc);
-							break;
-						default:
-							break;
-					}
-				}
-			}
 			updateChatMsgHint();
 			if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) {
 				activity.xmppConnectionService.markRead(conversation, true);
@@ -680,26 +686,11 @@ public class ConversationFragment extends Fragment {
 	}
 
 	protected void makeFingerprintWarning() {
-		Set<String> knownFingerprints = conversation.getContact()
-				.getOtrFingerprints();
-		if (conversation.hasValidOtrSession()
-				&& (!conversation.isMuted())
-				&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
-				.contains(conversation.getOtrFingerprint()))) {
-			showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
-					new OnClickListener() {
-
-						@Override
-						public void onClick(View v) {
-							if (conversation.getOtrFingerprint() != null) {
-								AlertDialog dialog = UIHelper
-										.getVerifyFingerprintDialog(
-												(ConversationActivity) getActivity(),
-												conversation, snackbar);
-								dialog.show();
-							}
-						}
-					});
+		if (conversation.smpRequested()) {
+			showSnackbar(R.string.smp_requested, R.string.verify, clickToVerify);
+		} else if (conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
+				&& (!conversation.isOtrFingerprintVerified())) {
+			showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
 		}
 	}
 
@@ -826,15 +817,15 @@ public class ConversationFragment extends Fragment {
 		final ConversationActivity activity = (ConversationActivity) getActivity();
 		final XmppConnectionService xmppService = activity.xmppConnectionService;
 		activity.selectPresence(message.getConversation(),
-			new OnPresenceSelected() {
+				new OnPresenceSelected() {
 
-				@Override
-				public void onPresenceSelected() {
-					message.setCounterpart(conversation.getNextCounterpart());
-					xmppService.sendMessage(message);
-					messageSent();
-				}
-			});
+					@Override
+					public void onPresenceSelected() {
+						message.setCounterpart(conversation.getNextCounterpart());
+						xmppService.sendMessage(message);
+						messageSent();
+					}
+				});
 	}
 
 	public void appendText(String text) {

src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java 🔗

@@ -0,0 +1,296 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.session.Session;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
+
+	public static final String ACTION_VERIFY_CONTACT = "verify_contact";
+
+	private RelativeLayout mVerificationAreaOne;
+	private RelativeLayout mVerificationAreaTwo;
+	private TextView mErrorNoSession;
+	private TextView mRemoteJid;
+	private TextView mRemoteFingerprint;
+	private TextView mYourFingerprint;
+	private EditText mSharedSecretHint;
+	private EditText mSharedSecretSecret;
+	private Button mButtonVerifyFingerprint;
+	private Button mButtonSharedSecretPositive;
+	private Button mButtonSharedSecretNegative;
+	private TextView mStatusMessage;
+	private Account mAccount;
+	private Conversation mConversation;
+
+	private View.OnClickListener mVerifyFingerprintListener = new View.OnClickListener() {
+
+		@Override
+		public void onClick(View view) {
+			mConversation.verifyOtrFingerprint();
+			finish();
+		}
+	};
+
+	private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
+		@Override
+		public void onClick(final View view) {
+			if (isAccountOnline()) {
+				final String question = mSharedSecretHint.getText().toString();
+				final String secret = mSharedSecretSecret.getText().toString();
+				initSmp(question, secret);
+				updateView();
+			}
+		}
+	};
+	private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() {
+		@Override
+		public void onClick(View view) {
+			if (isAccountOnline()) {
+				abortSmp();
+				updateView();
+			}
+		}
+	};
+	private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() {
+
+		@Override
+		public void onClick(View view) {
+			if (isAccountOnline()) {
+				final String question = mSharedSecretHint.getText().toString();
+				final String secret = mSharedSecretSecret.getText().toString();
+				respondSmp(question, secret);
+				updateView();
+			}
+		}
+	};
+	private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() {
+		@Override
+		public void onClick(View view) {
+			mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+			mConversation.smp().hint = null;
+			mConversation.smp().secret = null;
+			updateView();
+		}
+	};
+	private View.OnClickListener mFinishListener = new View.OnClickListener() {
+		@Override
+		public void onClick(View view) {
+			mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+			finish();
+		}
+	};
+
+	protected boolean initSmp(final String question, final String secret) {
+		final Session session = mConversation.getOtrSession();
+		if (session!=null) {
+			try {
+				session.initSmp(question, secret);
+				mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
+				return true;
+			} catch (OtrException e) {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	}
+
+	protected boolean abortSmp() {
+		final Session session = mConversation.getOtrSession();
+		if (session!=null) {
+			try {
+				session.abortSmp();
+				mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+				mConversation.smp().hint = null;
+				mConversation.smp().secret = null;
+				return true;
+			} catch (OtrException e) {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	}
+
+	protected boolean respondSmp(final String question, final String secret) {
+		final Session session = mConversation.getOtrSession();
+		if (session!=null) {
+			try {
+				session.respondSmp(question,secret);
+				return true;
+			} catch (OtrException e) {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	}
+
+	protected boolean isAccountOnline() {
+		if (this.mAccount.getStatus() != Account.STATUS_ONLINE) {
+			Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	protected boolean handleIntent(Intent intent) {
+		if (intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
+			try {
+				this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account")));
+			} catch (final InvalidJidException ignored) {
+				return false;
+			}
+			try {
+				this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact")));
+				if (this.mConversation == null) {
+					return false;
+				}
+			} catch (final InvalidJidException ignored) {
+				return false;
+			}
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	@Override
+	protected void onBackendConnected() {
+		if (handleIntent(getIntent())) {
+			updateView();
+		}
+	}
+
+	protected void updateView() {
+		if (this.mConversation.hasValidOtrSession()) {
+			this.mVerificationAreaOne.setVisibility(View.VISIBLE);
+			this.mVerificationAreaTwo.setVisibility(View.VISIBLE);
+			this.mErrorNoSession.setVisibility(View.GONE);
+			this.mYourFingerprint.setText(this.mAccount.getOtrFingerprint(xmppConnectionService));
+			this.mRemoteFingerprint.setText(this.mConversation.getOtrFingerprint());
+			this.mRemoteJid.setText(this.mConversation.getContact().getJid().toBareJid().toString());
+			Conversation.Smp smp = mConversation.smp();
+			Session session = mConversation.getOtrSession();
+			if (mConversation.isOtrFingerprintVerified()) {
+				deactivateButton(mButtonVerifyFingerprint, R.string.verified);
+			} else {
+				activateButton(mButtonVerifyFingerprint, R.string.verify, mVerifyFingerprintListener);
+			}
+			if (smp.status == Conversation.Smp.STATUS_NONE) {
+				activateButton(mButtonSharedSecretPositive, R.string.create, mCreateSharedSecretListener);
+				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
+				this.mSharedSecretHint.setFocusableInTouchMode(true);
+				this.mSharedSecretSecret.setFocusableInTouchMode(true);
+				this.mSharedSecretSecret.setText("");
+				this.mSharedSecretHint.setText("");
+				this.mSharedSecretHint.setVisibility(View.VISIBLE);
+				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+				this.mStatusMessage.setVisibility(View.GONE);
+			} else if (smp.status == Conversation.Smp.STATUS_CONTACT_REQUESTED) {
+				this.mSharedSecretHint.setFocusable(false);
+				this.mSharedSecretHint.setText(smp.hint);
+				this.mSharedSecretSecret.setFocusableInTouchMode(true);
+				this.mSharedSecretHint.setVisibility(View.VISIBLE);
+				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+				this.mStatusMessage.setVisibility(View.GONE);
+				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
+				activateButton(mButtonSharedSecretPositive, R.string.respond, mRespondSharedSecretListener);
+			} else if (smp.status == Conversation.Smp.STATUS_FAILED) {
+				activateButton(mButtonSharedSecretNegative, R.string.cancel, mFinishListener);
+				activateButton(mButtonSharedSecretPositive, R.string.try_again, mRetrySharedSecretListener);
+				this.mSharedSecretHint.setVisibility(View.GONE);
+				this.mSharedSecretSecret.setVisibility(View.GONE);
+				this.mStatusMessage.setVisibility(View.VISIBLE);
+				this.mStatusMessage.setText(R.string.secrets_do_not_match);
+				this.mStatusMessage.setTextColor(getWarningTextColor());
+			} else if (smp.status == Conversation.Smp.STATUS_VERIFIED) {
+				this.mSharedSecretHint.setVisibility(View.GONE);
+				this.mSharedSecretSecret.setVisibility(View.GONE);
+				this.mStatusMessage.setVisibility(View.VISIBLE);
+				this.mStatusMessage.setText(R.string.verified);
+				this.mStatusMessage.setTextColor(getPrimaryColor());
+				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
+				activateButton(mButtonSharedSecretPositive, R.string.finish, mFinishListener);
+			} else if (session != null && session.isSmpInProgress()) {
+				deactivateButton(mButtonSharedSecretPositive, R.string.in_progress);
+				activateButton(mButtonSharedSecretNegative, R.string.cancel, mCancelSharedSecretListener);
+				this.mSharedSecretHint.setVisibility(View.VISIBLE);
+				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+				this.mSharedSecretHint.setFocusable(false);
+				this.mSharedSecretSecret.setFocusable(false);
+			}
+		} else {
+			this.mVerificationAreaOne.setVisibility(View.GONE);
+			this.mVerificationAreaTwo.setVisibility(View.GONE);
+			this.mErrorNoSession.setVisibility(View.VISIBLE);
+		}
+	}
+
+	protected void activateButton(Button button, int text, View.OnClickListener listener) {
+		button.setEnabled(true);
+		button.setTextColor(getPrimaryTextColor());
+		button.setText(text);
+		button.setOnClickListener(listener);
+	}
+
+	protected void deactivateButton(Button button, int text) {
+		button.setEnabled(false);
+		button.setTextColor(getSecondaryTextColor());
+		button.setText(text);
+		button.setOnClickListener(null);
+	}
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_verify_otr);
+		this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
+		this.mRemoteJid = (TextView) findViewById(R.id.remote_jid);
+		this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
+		this.mButtonSharedSecretNegative = (Button) findViewById(R.id.button_shared_secret_negative);
+		this.mButtonSharedSecretPositive = (Button) findViewById(R.id.button_shared_secret_positive);
+		this.mButtonVerifyFingerprint = (Button) findViewById(R.id.button_verify_fingerprint);
+		this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
+		this.mSharedSecretHint = (EditText) findViewById(R.id.shared_secret_hint);
+		this.mStatusMessage= (TextView) findViewById(R.id.status_message);
+		this.mVerificationAreaOne = (RelativeLayout) findViewById(R.id.verification_area_one);
+		this.mVerificationAreaTwo = (RelativeLayout) findViewById(R.id.verification_area_two);
+		this.mErrorNoSession = (TextView) findViewById(R.id.error_no_session);
+	}
+
+	@Override
+	protected String getShareableUri() {
+		if (mAccount!=null) {
+			return "xmpp:"+mAccount.getJid().toBareJid();
+		} else {
+			return "";
+		}
+	}
+
+	public void onConversationUpdate() {
+		runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				updateView();
+			}
+		});
+	}
+}

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -685,6 +685,7 @@ public abstract class XmppActivity extends Activity {
 	}
 
 	protected Bitmap createQrCodeBitmap(String input, int size) {
+		Log.d(Config.LOGTAG,"qr code requested size: "+size);
 		try {
 			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
 			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
@@ -700,6 +701,7 @@ public abstract class XmppActivity extends Activity {
 				}
 			}
 			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
 			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
 			return bitmap;
 		} catch (final WriterException e) {

src/main/java/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -148,40 +148,6 @@ public class UIHelper {
 		mNotificationManager.notify(1111, notification);
 	}
 
-	@SuppressLint("InflateParams")
-	public static AlertDialog getVerifyFingerprintDialog(
-			final ConversationActivity activity,
-			final Conversation conversation, final View 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().toString());
-		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.syncRosterToDisk(account);
-			}
-		});
-		builder.setView(view);
-		return builder.create();
-	}
-
 	private final static class EmoticonPattern {
 		Pattern pattern;
 		String replacement;

src/main/res/layout/activity_verify_otr.xml 🔗

@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:background="@color/secondarybackground">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/error_no_session"
+            android:layout_margin="16dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/no_otr_session_found"
+            android:layout_gravity="center_horizontal"
+            android:textColor="@color/primarytext"
+            android:textSize="?attr/TextSizeBody"
+            />
+        <RelativeLayout
+            android:id="@+id/verification_area_one"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:background="@drawable/infocard_border"
+            android:layout_margin="8dp">
+        <LinearLayout
+            android:id="@+id/fingerprint_area"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="16dp"
+            android:orientation="vertical">
+            <TextView
+                android:id="@+id/remote_jid"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/primarytext"
+                android:textSize="?attr/TextSizeHeadline"/>
+            <TextView
+                android:layout_marginTop="16dp"
+                android:id="@+id/your_fingerprint"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/primarytext"
+                android:textSize="?attr/TextSizeBody"
+                android:typeface="monospace" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/secondarytext"
+                android:textSize="?attr/TextSizeInfo"
+                android:text="@string/your_fingerprint"/>
+            <TextView
+                android:layout_marginTop="16dp"
+                android:id="@+id/remote_fingerprint"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/primarytext"
+                android:textSize="?attr/TextSizeBody"
+                android:typeface="monospace" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/secondarytext"
+                android:textSize="?attr/TextSizeInfo"
+                android:text="@string/remote_fingerprint"/>
+        </LinearLayout>
+            <LinearLayout
+                android:layout_below="@+id/fingerprint_area"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentRight="true" >
+
+                <Button
+                    style="?android:attr/borderlessButtonStyle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:visibility="invisible" />
+
+                <View
+                    android:layout_width="1dp"
+                    android:layout_height="fill_parent"
+                    android:layout_marginBottom="7dp"
+                    android:layout_marginTop="7dp"
+                    android:background="@color/divider"
+                    android:visibility="invisible"/>
+
+                <Button
+                    android:id="@+id/button_verify_fingerprint"
+                    style="?android:attr/borderlessButtonStyle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:text="@string/verify"
+                    android:textColor="@color/primarytext" />
+            </LinearLayout>
+        </RelativeLayout>
+        <RelativeLayout
+            android:id="@+id/verification_area_two"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:background="@drawable/infocard_border">
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:orientation="vertical"
+                android:id="@+id/shared_secret_box"
+                android:padding="16dp">
+                <TextView
+                    android:text="@string/smp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/primarytext"
+                    android:textSize="?attr/TextSizeHeadline"
+                    android:layout_marginBottom="16dp"
+                    />
+                <TextView
+                    android:id="@+id/status_message"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/verified"
+                    android:layout_gravity="center_horizontal"
+                    android:textSize="?attr/TextSizeHeadline"
+                    android:textStyle="bold"
+                    android:visibility="gone"/>
+                <EditText
+                    android:id="@+id/shared_secret_hint"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="textAutoComplete"
+                    android:hint="@string/shared_secret_hint"
+                    android:textColor="@color/primarytext"
+                    android:textColorHint="@color/secondarytext"
+                    android:textSize="?attr/TextSizeBody"
+                    android:layout_marginBottom="8dp"/>
+                <EditText
+                    android:id="@+id/shared_secret_secret"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/shared_secret_secret"
+                    android:inputType="textPassword"
+                    android:textColor="@color/primarytext"
+                    android:textColorHint="@color/secondarytext"
+                    android:textSize="?attr/TextSizeBody" />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_below="@+id/shared_secret_box"
+                android:id="@+id/button_bar"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentRight="true" >
+
+                <Button
+                    android:id="@+id/button_shared_secret_negative"
+                    style="?android:attr/borderlessButtonStyle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:enabled="false"
+                    android:text="@string/cancel"
+                    android:textColor="@color/secondarytext"/>
+
+                <View
+                    android:layout_width="1dp"
+                    android:layout_height="fill_parent"
+                    android:layout_marginBottom="7dp"
+                    android:layout_marginTop="7dp"
+                    android:background="@color/divider" />
+
+                <Button
+                    android:id="@+id/button_shared_secret_positive"
+                    style="?android:attr/borderlessButtonStyle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:text="@string/create"
+                    android:textColor="@color/primarytext" />
+            </LinearLayout>
+        </RelativeLayout>
+    </LinearLayout>
+</ScrollView>

src/main/res/layout/dialog_verify_otr.xml 🔗

@@ -1,60 +0,0 @@
-<?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:paddingBottom="16dp"
-    android:paddingLeft="8dp"
-    android:paddingRight="8dp" >
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingTop="8dp"
-        android:text="@string/account_settings_jabber_id"
-        android:textColor="@color/primarytext"
-        android:textSize="?attr/TextSizeHeadline" />
-
-    <TextView
-        android:id="@+id/verify_otr_jid"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="8dp"
-        android:textColor="@color/secondarytext"
-        android:textSize="?attr/TextSizeBody" />
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingTop="8dp"
-        android:text="@string/otr_fingerprint"
-        android:textColor="@color/primarytext"
-        android:textSize="?attr/TextSizeHeadline" />
-
-    <TextView
-        android:id="@+id/verify_otr_fingerprint"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="8dp"
-        android:textColor="@color/secondarytext"
-        android:textSize="?attr/TextSizeBody"
-        android:typeface="monospace" />
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingTop="8dp"
-        android:text="@string/your_fingerprint"
-        android:textColor="@color/primarytext"
-        android:textSize="?attr/TextSizeHeadline" />
-
-    <TextView
-        android:id="@+id/verify_otr_yourprint"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="8dp"
-        android:textColor="@color/secondarytext"
-        android:textSize="?attr/TextSizeBody"
-        android:typeface="monospace" />
-
-</LinearLayout>

src/main/res/values/strings.xml 🔗

@@ -311,4 +311,21 @@
     <string name="scan_qr_code">Scan QR code</string>
     <string name="show_qr_code">Show QR code</string>
     <string name="account_details">Account details</string>
+    <string name="verify_otr">Verify OTR</string>
+    <string name="remote_fingerprint">Remote Fingerprint</string>
+    <string name="scan">scan</string>
+    <string name="or_touch_phones">(or touch phones)</string>
+    <string name="smp">Socialist Millionaire Protocol</string>
+    <string name="shared_secret_hint">Hint or Question</string>
+    <string name="shared_secret_secret">Shared Secret</string>
+    <string name="confirm">Confirm</string>
+    <string name="in_progress">In progress</string>
+    <string name="respond">Respond</string>
+    <string name="failed">Failed</string>
+    <string name="secrets_do_not_match">Secrets do not match</string>
+    <string name="try_again">Try again</string>;
+    <string name="finish">Finish</string>
+    <string name="verified">Verified!</string>
+    <string name="smp_requested">Contact requested SMP verification</string>
+    <string name="no_otr_session_found">No valid OTR session has been found!</string>
 </resources>