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