VerifyOTRActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.AlertDialog;
  4import android.content.DialogInterface;
  5import android.content.Intent;
  6import android.os.Bundle;
  7import android.view.Menu;
  8import android.view.MenuItem;
  9import android.view.View;
 10import android.widget.Button;
 11import android.widget.EditText;
 12import android.widget.RelativeLayout;
 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
 36	private RelativeLayout mVerificationAreaOne;
 37	private RelativeLayout mVerificationAreaTwo;
 38	private TextView mErrorNoSession;
 39	private TextView mRemoteJid;
 40	private TextView mRemoteFingerprint;
 41	private TextView mYourFingerprint;
 42	private EditText mSharedSecretHint;
 43	private EditText mSharedSecretSecret;
 44	private Button mButtonScanQrCode;
 45	private Button mButtonShowQrCode;
 46	private Button mButtonSharedSecretPositive;
 47	private Button mButtonSharedSecretNegative;
 48	private TextView mStatusMessage;
 49	private Account mAccount;
 50	private Conversation mConversation;
 51
 52	private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
 53
 54		@Override
 55		public void onClick(DialogInterface dialogInterface, int click) {
 56			mConversation.verifyOtrFingerprint();
 57			updateView();
 58			xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
 59		}
 60	};
 61
 62	private View.OnClickListener mShowQrCodeListener = new View.OnClickListener() {
 63		@Override
 64		public void onClick(final View view) {
 65			showQrCode();
 66		}
 67	};
 68
 69	private View.OnClickListener mScanQrCodeListener = new View.OnClickListener() {
 70
 71		@Override
 72		public void onClick(View view) {
 73			new IntentIntegrator(VerifyOTRActivity.this).initiateScan();
 74		}
 75
 76	};
 77
 78	private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
 79		@Override
 80		public void onClick(final View view) {
 81			if (isAccountOnline()) {
 82				final String question = mSharedSecretHint.getText().toString();
 83				final String secret = mSharedSecretSecret.getText().toString();
 84				initSmp(question, secret);
 85				updateView();
 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 = mSharedSecretHint.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	private XmppUri mPendingUri = null;
128
129	protected boolean initSmp(final String question, final String secret) {
130		final Session session = mConversation.getOtrSession();
131		if (session!=null) {
132			try {
133				session.initSmp(question, secret);
134				mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
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 void 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		} else {
183			Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();
184		}
185	}
186
187	protected boolean isAccountOnline() {
188		if (this.mAccount.getStatus() != Account.State.ONLINE) {
189			Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
190			return false;
191		} else {
192			return true;
193		}
194	}
195
196	protected boolean handleIntent(Intent intent) {
197		if (intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
198			try {
199				this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account")));
200			} catch (final InvalidJidException ignored) {
201				return false;
202			}
203			try {
204				this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact")));
205				if (this.mConversation == null) {
206					return false;
207				}
208			} catch (final InvalidJidException ignored) {
209				return false;
210			}
211			return true;
212		} else {
213			return false;
214		}
215	}
216
217	@Override
218	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
219		if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
220			IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
221			if (scanResult != null && scanResult.getFormatName() != null) {
222				String data = scanResult.getContents();
223				XmppUri uri = new XmppUri(data);
224				if (xmppConnectionServiceBound) {
225					verifyWithUri(uri);
226				} else {
227					this.mPendingUri = uri;
228				}
229			}
230		}
231		super.onActivityResult(requestCode, requestCode, intent);
232	}
233
234	@Override
235	protected void onBackendConnected() {
236		if (handleIntent(getIntent())) {
237			if (mPendingUri!=null) {
238				verifyWithUri(mPendingUri);
239				mPendingUri = null;
240			}
241			updateView();
242		}
243	}
244
245	protected void updateView() {
246		if (this.mConversation.hasValidOtrSession()) {
247			invalidateOptionsMenu();
248			this.mVerificationAreaOne.setVisibility(View.VISIBLE);
249			this.mVerificationAreaTwo.setVisibility(View.VISIBLE);
250			this.mErrorNoSession.setVisibility(View.GONE);
251			this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
252			this.mRemoteFingerprint.setText(this.mConversation.getOtrFingerprint());
253			this.mRemoteJid.setText(this.mConversation.getContact().getJid().toBareJid().toString());
254			Conversation.Smp smp = mConversation.smp();
255			Session session = mConversation.getOtrSession();
256			if (mConversation.isOtrFingerprintVerified()) {
257				deactivateButton(mButtonScanQrCode, R.string.verified);
258			} else {
259				activateButton(mButtonScanQrCode, R.string.scan_qr_code, mScanQrCodeListener);
260			}
261			if (smp.status == Conversation.Smp.STATUS_NONE) {
262				activateButton(mButtonSharedSecretPositive, R.string.create, mCreateSharedSecretListener);
263				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
264				this.mSharedSecretHint.setFocusableInTouchMode(true);
265				this.mSharedSecretSecret.setFocusableInTouchMode(true);
266				this.mSharedSecretSecret.setText("");
267				this.mSharedSecretHint.setText("");
268				this.mSharedSecretHint.setVisibility(View.VISIBLE);
269				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
270				this.mStatusMessage.setVisibility(View.GONE);
271			} else if (smp.status == Conversation.Smp.STATUS_CONTACT_REQUESTED) {
272				this.mSharedSecretHint.setFocusable(false);
273				this.mSharedSecretHint.setText(smp.hint);
274				this.mSharedSecretSecret.setFocusableInTouchMode(true);
275				this.mSharedSecretHint.setVisibility(View.VISIBLE);
276				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
277				this.mStatusMessage.setVisibility(View.GONE);
278				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
279				activateButton(mButtonSharedSecretPositive, R.string.respond, mRespondSharedSecretListener);
280			} else if (smp.status == Conversation.Smp.STATUS_FAILED) {
281				activateButton(mButtonSharedSecretNegative, R.string.cancel, mFinishListener);
282				activateButton(mButtonSharedSecretPositive, R.string.try_again, mRetrySharedSecretListener);
283				this.mSharedSecretHint.setVisibility(View.GONE);
284				this.mSharedSecretSecret.setVisibility(View.GONE);
285				this.mStatusMessage.setVisibility(View.VISIBLE);
286				this.mStatusMessage.setText(R.string.secrets_do_not_match);
287				this.mStatusMessage.setTextColor(getWarningTextColor());
288			} else if (smp.status == Conversation.Smp.STATUS_VERIFIED) {
289				this.mSharedSecretHint.setVisibility(View.GONE);
290				this.mSharedSecretSecret.setVisibility(View.GONE);
291				this.mStatusMessage.setVisibility(View.VISIBLE);
292				this.mStatusMessage.setText(R.string.verified);
293				this.mStatusMessage.setTextColor(getPrimaryColor());
294				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
295				activateButton(mButtonSharedSecretPositive, R.string.finish, mFinishListener);
296			} else if (session != null && session.isSmpInProgress()) {
297				deactivateButton(mButtonSharedSecretPositive, R.string.in_progress);
298				activateButton(mButtonSharedSecretNegative, R.string.cancel, mCancelSharedSecretListener);
299				this.mSharedSecretHint.setVisibility(View.VISIBLE);
300				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
301				this.mSharedSecretHint.setFocusable(false);
302				this.mSharedSecretSecret.setFocusable(false);
303			}
304		} else {
305			this.mVerificationAreaOne.setVisibility(View.GONE);
306			this.mVerificationAreaTwo.setVisibility(View.GONE);
307			this.mErrorNoSession.setVisibility(View.VISIBLE);
308		}
309	}
310
311	protected void activateButton(Button button, int text, View.OnClickListener listener) {
312		button.setEnabled(true);
313		button.setTextColor(getPrimaryTextColor());
314		button.setText(text);
315		button.setOnClickListener(listener);
316	}
317
318	protected void deactivateButton(Button button, int text) {
319		button.setEnabled(false);
320		button.setTextColor(getSecondaryTextColor());
321		button.setText(text);
322		button.setOnClickListener(null);
323	}
324
325	@Override
326	protected void onCreate(Bundle savedInstanceState) {
327		super.onCreate(savedInstanceState);
328		setContentView(R.layout.activity_verify_otr);
329		this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
330		this.mRemoteJid = (TextView) findViewById(R.id.remote_jid);
331		this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
332		this.mButtonSharedSecretNegative = (Button) findViewById(R.id.button_shared_secret_negative);
333		this.mButtonSharedSecretPositive = (Button) findViewById(R.id.button_shared_secret_positive);
334		this.mButtonScanQrCode = (Button) findViewById(R.id.button_scan_qr_code);
335		this.mButtonShowQrCode = (Button) findViewById(R.id.button_show_qr_code);
336		this.mButtonShowQrCode.setOnClickListener(this.mShowQrCodeListener);
337		this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
338		this.mSharedSecretHint = (EditText) findViewById(R.id.shared_secret_hint);
339		this.mStatusMessage= (TextView) findViewById(R.id.status_message);
340		this.mVerificationAreaOne = (RelativeLayout) findViewById(R.id.verification_area_one);
341		this.mVerificationAreaTwo = (RelativeLayout) findViewById(R.id.verification_area_two);
342		this.mErrorNoSession = (TextView) findViewById(R.id.error_no_session);
343	}
344
345	@Override
346	public boolean onCreateOptionsMenu(Menu menu) {
347		getMenuInflater().inflate(R.menu.verify_otr, menu);
348		if (mConversation != null && mConversation.isOtrFingerprintVerified()) {
349			MenuItem manuallyVerifyItem = menu.findItem(R.id.manually_verify);
350			manuallyVerifyItem.setVisible(false);
351		}
352		return true;
353	}
354
355	@Override
356	public boolean onOptionsItemSelected(MenuItem menuItem) {
357		if (menuItem.getItemId() == R.id.manually_verify) {
358			showManuallyVerifyDialog();
359			return true;
360		} else {
361			return super.onOptionsItemSelected(menuItem);
362		}
363	}
364
365	private void showManuallyVerifyDialog() {
366		AlertDialog.Builder builder = new AlertDialog.Builder(this);
367		builder.setTitle(R.string.manually_verify);
368		builder.setMessage(R.string.are_you_sure_verify_fingerprint);
369		builder.setNegativeButton(R.string.cancel, null);
370		builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
371		builder.create().show();
372	}
373
374	@Override
375	protected String getShareableUri() {
376		if (mAccount!=null) {
377			return mAccount.getShareableUri();
378		} else {
379			return "";
380		}
381	}
382
383	public void onConversationUpdate() {
384		runOnUiThread(new Runnable() {
385			@Override
386			public void run() {
387				updateView();
388			}
389		});
390	}
391}