VerifyOTRActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.ActionBar;
  4import android.app.AlertDialog;
  5import android.content.DialogInterface;
  6import android.content.Intent;
  7import android.os.Bundle;
  8import android.view.Menu;
  9import android.view.View;
 10import android.widget.Button;
 11import android.widget.EditText;
 12import android.widget.LinearLayout;
 13import android.widget.TextView;
 14import android.widget.Toast;
 15
 16import com.google.zxing.integration.android.IntentIntegrator;
 17import com.google.zxing.integration.android.IntentResult;
 18
 19import net.java.otr4j.OtrException;
 20import net.java.otr4j.session.Session;
 21
 22import eu.siacs.conversations.R;
 23import eu.siacs.conversations.entities.Account;
 24import eu.siacs.conversations.entities.Contact;
 25import eu.siacs.conversations.entities.Conversation;
 26import eu.siacs.conversations.services.XmppConnectionService;
 27import eu.siacs.conversations.utils.CryptoHelper;
 28import eu.siacs.conversations.utils.XmppUri;
 29import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 30import eu.siacs.conversations.xmpp.jid.Jid;
 31
 32public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
 33
 34	public static final String ACTION_VERIFY_CONTACT = "verify_contact";
 35	public static final int MODE_SCAN_FINGERPRINT = - 0x0502;
 36	public static final int MODE_ASK_QUESTION = 0x0503;
 37	public static final int MODE_ANSWER_QUESTION = 0x0504;
 38	public static final int MODE_MANUAL_VERIFICATION = 0x0505;
 39
 40	private LinearLayout mManualVerificationArea;
 41	private LinearLayout mSmpVerificationArea;
 42	private TextView mRemoteFingerprint;
 43	private TextView mYourFingerprint;
 44	private TextView mVerificationExplain;
 45	private TextView mStatusMessage;
 46	private TextView mSharedSecretHint;
 47	private EditText mSharedSecretHintEditable;
 48	private EditText mSharedSecretSecret;
 49	private Button mLeftButton;
 50	private Button mRightButton;
 51	private Account mAccount;
 52	private Conversation mConversation;
 53	private int mode = MODE_MANUAL_VERIFICATION;
 54	private XmppUri mPendingUri = null;
 55
 56	private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
 57
 58		@Override
 59		public void onClick(DialogInterface dialogInterface, int click) {
 60			mConversation.verifyOtrFingerprint();
 61			xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
 62			Toast.makeText(VerifyOTRActivity.this,R.string.verified,Toast.LENGTH_SHORT).show();
 63			finish();
 64		}
 65	};
 66
 67	private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
 68		@Override
 69		public void onClick(final View view) {
 70			if (isAccountOnline()) {
 71				final String question = mSharedSecretHintEditable.getText().toString();
 72				final String secret = mSharedSecretSecret.getText().toString();
 73				if (question.trim().isEmpty()) {
 74					mSharedSecretHintEditable.requestFocus();
 75					mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty));
 76				} else if (secret.trim().isEmpty()) {
 77					mSharedSecretSecret.requestFocus();
 78					mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty));
 79				} else {
 80					mSharedSecretSecret.setError(null);
 81					mSharedSecretHintEditable.setError(null);
 82					initSmp(question, secret);
 83					updateView();
 84				}
 85			}
 86		}
 87	};
 88	private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() {
 89		@Override
 90		public void onClick(View view) {
 91			if (isAccountOnline()) {
 92				abortSmp();
 93				updateView();
 94			}
 95		}
 96	};
 97	private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() {
 98
 99		@Override
100		public void onClick(View view) {
101			if (isAccountOnline()) {
102				final String question = mSharedSecretHintEditable.getText().toString();
103				final String secret = mSharedSecretSecret.getText().toString();
104				respondSmp(question, secret);
105				updateView();
106			}
107		}
108	};
109	private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() {
110		@Override
111		public void onClick(View view) {
112			mConversation.smp().status = Conversation.Smp.STATUS_NONE;
113			mConversation.smp().hint = null;
114			mConversation.smp().secret = null;
115			updateView();
116		}
117	};
118	private View.OnClickListener mFinishListener = new View.OnClickListener() {
119		@Override
120		public void onClick(View view) {
121			mConversation.smp().status = Conversation.Smp.STATUS_NONE;
122			finish();
123		}
124	};
125
126	protected boolean initSmp(final String question, final String secret) {
127		final Session session = mConversation.getOtrSession();
128		if (session!=null) {
129			try {
130				session.initSmp(question, secret);
131				mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
132				mConversation.smp().secret = secret;
133				mConversation.smp().hint = question;
134				return true;
135			} catch (OtrException e) {
136				return false;
137			}
138		} else {
139			return false;
140		}
141	}
142
143	protected boolean abortSmp() {
144		final Session session = mConversation.getOtrSession();
145		if (session!=null) {
146			try {
147				session.abortSmp();
148				mConversation.smp().status = Conversation.Smp.STATUS_NONE;
149				mConversation.smp().hint = null;
150				mConversation.smp().secret = null;
151				return true;
152			} catch (OtrException e) {
153				return false;
154			}
155		} else {
156			return false;
157		}
158	}
159
160	protected boolean respondSmp(final String question, final String secret) {
161		final Session session = mConversation.getOtrSession();
162		if (session!=null) {
163			try {
164				session.respondSmp(question,secret);
165				return true;
166			} catch (OtrException e) {
167				return false;
168			}
169		} else {
170			return false;
171		}
172	}
173
174	protected boolean verifyWithUri(XmppUri uri) {
175		Contact contact = mConversation.getContact();
176		if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.getFingerprint() != null) {
177			contact.addOtrFingerprint(uri.getFingerprint());
178			Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show();
179			updateView();
180			xmppConnectionService.syncRosterToDisk(contact.getAccount());
181			return true;
182		} else {
183			Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();
184			return false;
185		}
186	}
187
188	protected boolean isAccountOnline() {
189		if (this.mAccount.getStatus() != Account.State.ONLINE) {
190			Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
191			return false;
192		} else {
193			return true;
194		}
195	}
196
197	protected boolean handleIntent(Intent intent) {
198		if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
199			try {
200				this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account")));
201			} catch (final InvalidJidException ignored) {
202				return false;
203			}
204			try {
205				this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact")));
206				if (this.mConversation == null) {
207					return false;
208				}
209			} catch (final InvalidJidException ignored) {
210				return false;
211			}
212			this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION);
213			if (this.mode == MODE_SCAN_FINGERPRINT) {
214				new IntentIntegrator(this).initiateScan();
215				return false;
216			}
217			return true;
218		} else {
219			return false;
220		}
221	}
222
223	@Override
224	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
225		if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
226			IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
227			if (scanResult != null && scanResult.getFormatName() != null) {
228				String data = scanResult.getContents();
229				XmppUri uri = new XmppUri(data);
230				if (xmppConnectionServiceBound) {
231					verifyWithUri(uri);
232					finish();
233				} else {
234					this.mPendingUri = uri;
235				}
236			} else {
237				finish();
238			}
239		}
240		super.onActivityResult(requestCode, requestCode, intent);
241	}
242
243	@Override
244	protected void onBackendConnected() {
245		if (handleIntent(getIntent())) {
246			updateView();
247		} else if (mPendingUri!=null) {
248			verifyWithUri(mPendingUri);
249			finish();
250			mPendingUri = null;
251		}
252		setIntent(null);
253	}
254
255	protected void updateView() {
256		if (this.mConversation.hasValidOtrSession()) {
257			final ActionBar actionBar = getActionBar();
258			this.mVerificationExplain.setText(R.string.no_otr_session_found);
259			invalidateOptionsMenu();
260			switch(this.mode) {
261				case MODE_ASK_QUESTION:
262					if (actionBar != null ) {
263						actionBar.setTitle(R.string.ask_question);
264					}
265					this.updateViewAskQuestion();
266					break;
267				case MODE_ANSWER_QUESTION:
268					if (actionBar != null ) {
269						actionBar.setTitle(R.string.smp_requested);
270					}
271					this.updateViewAnswerQuestion();
272					break;
273				case MODE_MANUAL_VERIFICATION:
274				default:
275					if (actionBar != null ) {
276						actionBar.setTitle(R.string.manually_verify);
277					}
278					this.updateViewManualVerification();
279					break;
280			}
281		} else {
282			this.mManualVerificationArea.setVisibility(View.GONE);
283			this.mSmpVerificationArea.setVisibility(View.GONE);
284		}
285	}
286
287	protected void updateViewManualVerification() {
288		this.mVerificationExplain.setText(R.string.manual_verification_explanation);
289		this.mManualVerificationArea.setVisibility(View.VISIBLE);
290		this.mSmpVerificationArea.setVisibility(View.GONE);
291		this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
292		this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint()));
293		if (this.mConversation.isOtrFingerprintVerified()) {
294			deactivateButton(this.mRightButton,R.string.verified);
295			activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
296		} else {
297			activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
298			activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() {
299				@Override
300				public void onClick(View view) {
301					showManuallyVerifyDialog();
302				}
303			});
304		}
305	}
306
307	protected void updateViewAskQuestion() {
308		this.mManualVerificationArea.setVisibility(View.GONE);
309		this.mSmpVerificationArea.setVisibility(View.VISIBLE);
310		this.mVerificationExplain.setText(R.string.smp_explain_question);
311		final int smpStatus = this.mConversation.smp().status;
312		switch (smpStatus) {
313			case Conversation.Smp.STATUS_WE_REQUESTED:
314				this.mStatusMessage.setVisibility(View.GONE);
315				this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
316				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
317				this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint);
318				this.mSharedSecretSecret.setText(this.mConversation.smp().secret);
319				this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener);
320				this.deactivateButton(this.mRightButton, R.string.in_progress);
321				break;
322			case Conversation.Smp.STATUS_FAILED:
323				this.mStatusMessage.setVisibility(View.GONE);
324				this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
325				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
326				this.mSharedSecretSecret.requestFocus();
327				this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
328				this.deactivateButton(this.mLeftButton, R.string.cancel);
329				this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener);
330				break;
331			case Conversation.Smp.STATUS_VERIFIED:
332				this.mSharedSecretHintEditable.setText("");
333				this.mSharedSecretHintEditable.setVisibility(View.GONE);
334				this.mSharedSecretSecret.setText("");
335				this.mSharedSecretSecret.setVisibility(View.GONE);
336				this.mStatusMessage.setVisibility(View.VISIBLE);
337				this.deactivateButton(this.mLeftButton, R.string.cancel);
338				this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
339				break;
340			default:
341				this.mStatusMessage.setVisibility(View.GONE);
342				this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
343				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
344				this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
345				this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener);
346				break;
347		}
348	}
349
350	protected void updateViewAnswerQuestion() {
351		this.mManualVerificationArea.setVisibility(View.GONE);
352		this.mSmpVerificationArea.setVisibility(View.VISIBLE);
353		this.mVerificationExplain.setText(R.string.smp_explain_answer);
354		this.mSharedSecretHintEditable.setVisibility(View.GONE);
355		this.mSharedSecretHint.setVisibility(View.VISIBLE);
356		this.deactivateButton(this.mLeftButton, R.string.cancel);
357		final int smpStatus = this.mConversation.smp().status;
358		switch (smpStatus) {
359			case Conversation.Smp.STATUS_CONTACT_REQUESTED:
360				this.mStatusMessage.setVisibility(View.GONE);
361				this.mSharedSecretHint.setText(this.mConversation.smp().hint);
362				this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener);
363				break;
364			case Conversation.Smp.STATUS_VERIFIED:
365				this.mSharedSecretHintEditable.setText("");
366				this.mSharedSecretHintEditable.setVisibility(View.GONE);
367				this.mSharedSecretHint.setVisibility(View.GONE);
368				this.mSharedSecretSecret.setText("");
369				this.mSharedSecretSecret.setVisibility(View.GONE);
370				this.mStatusMessage.setVisibility(View.VISIBLE);
371				this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
372				break;
373			case Conversation.Smp.STATUS_FAILED:
374			default:
375				this.mSharedSecretSecret.requestFocus();
376				this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
377				this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener);
378				break;
379		}
380	}
381
382	protected void activateButton(Button button, int text, View.OnClickListener listener) {
383		button.setEnabled(true);
384		button.setTextColor(getPrimaryTextColor());
385		button.setText(text);
386		button.setOnClickListener(listener);
387	}
388
389	protected void deactivateButton(Button button, int text) {
390		button.setEnabled(false);
391		button.setTextColor(getSecondaryTextColor());
392		button.setText(text);
393		button.setOnClickListener(null);
394	}
395
396	@Override
397	protected void onCreate(Bundle savedInstanceState) {
398		super.onCreate(savedInstanceState);
399		setContentView(R.layout.activity_verify_otr);
400		this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
401		this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
402		this.mLeftButton = (Button) findViewById(R.id.left_button);
403		this.mRightButton = (Button) findViewById(R.id.right_button);
404		this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation);
405		this.mStatusMessage = (TextView) findViewById(R.id.status_message);
406		this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
407		this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable);
408		this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint);
409		this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area);
410		this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area);
411	}
412
413	@Override
414	public boolean onCreateOptionsMenu(final Menu menu) {
415		super.onCreateOptionsMenu(menu);
416		getMenuInflater().inflate(R.menu.verify_otr, menu);
417		return true;
418	}
419
420	private void showManuallyVerifyDialog() {
421		AlertDialog.Builder builder = new AlertDialog.Builder(this);
422		builder.setTitle(R.string.manually_verify);
423		builder.setMessage(R.string.are_you_sure_verify_fingerprint);
424		builder.setNegativeButton(R.string.cancel, null);
425		builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
426		builder.create().show();
427	}
428
429	@Override
430	protected String getShareableUri() {
431		if (mAccount!=null) {
432			return mAccount.getShareableUri();
433		} else {
434			return "";
435		}
436	}
437
438	public void onConversationUpdate() {
439		refreshUi();
440	}
441
442	@Override
443	protected void refreshUiReal() {
444		updateView();
445	}
446}