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