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_FINISHED) {
289				this.mSharedSecretHint.setText("");
290				this.mSharedSecretHint.setVisibility(View.GONE);
291				this.mSharedSecretSecret.setText("");
292				this.mSharedSecretSecret.setVisibility(View.GONE);
293				this.mStatusMessage.setVisibility(View.VISIBLE);
294				this.mStatusMessage.setTextColor(getPrimaryColor());
295				deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
296				if (mConversation.isOtrFingerprintVerified()) {
297					activateButton(mButtonSharedSecretPositive, R.string.finish, mFinishListener);
298					this.mStatusMessage.setText(R.string.verified);
299				} else {
300					activateButton(mButtonSharedSecretPositive,R.string.reset,mRetrySharedSecretListener);
301					this.mStatusMessage.setText(R.string.secret_accepted);
302				}
303			} else if (session != null && session.isSmpInProgress()) {
304				deactivateButton(mButtonSharedSecretPositive, R.string.in_progress);
305				activateButton(mButtonSharedSecretNegative, R.string.cancel, mCancelSharedSecretListener);
306				this.mSharedSecretHint.setVisibility(View.VISIBLE);
307				this.mSharedSecretSecret.setVisibility(View.VISIBLE);
308				this.mSharedSecretHint.setFocusable(false);
309				this.mSharedSecretSecret.setFocusable(false);
310			}
311		} else {
312			this.mVerificationAreaOne.setVisibility(View.GONE);
313			this.mVerificationAreaTwo.setVisibility(View.GONE);
314			this.mErrorNoSession.setVisibility(View.VISIBLE);
315		}
316	}
317
318	protected void activateButton(Button button, int text, View.OnClickListener listener) {
319		button.setEnabled(true);
320		button.setTextColor(getPrimaryTextColor());
321		button.setText(text);
322		button.setOnClickListener(listener);
323	}
324
325	protected void deactivateButton(Button button, int text) {
326		button.setEnabled(false);
327		button.setTextColor(getSecondaryTextColor());
328		button.setText(text);
329		button.setOnClickListener(null);
330	}
331
332	@Override
333	protected void onCreate(Bundle savedInstanceState) {
334		super.onCreate(savedInstanceState);
335		setContentView(R.layout.activity_verify_otr);
336		this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
337		this.mRemoteJid = (TextView) findViewById(R.id.remote_jid);
338		this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
339		this.mButtonSharedSecretNegative = (Button) findViewById(R.id.button_shared_secret_negative);
340		this.mButtonSharedSecretPositive = (Button) findViewById(R.id.button_shared_secret_positive);
341		this.mButtonScanQrCode = (Button) findViewById(R.id.button_scan_qr_code);
342		this.mButtonShowQrCode = (Button) findViewById(R.id.button_show_qr_code);
343		this.mButtonShowQrCode.setOnClickListener(this.mShowQrCodeListener);
344		this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
345		this.mSharedSecretHint = (EditText) findViewById(R.id.shared_secret_hint);
346		this.mStatusMessage= (TextView) findViewById(R.id.status_message);
347		this.mVerificationAreaOne = (RelativeLayout) findViewById(R.id.verification_area_one);
348		this.mVerificationAreaTwo = (RelativeLayout) findViewById(R.id.verification_area_two);
349		this.mErrorNoSession = (TextView) findViewById(R.id.error_no_session);
350	}
351
352	@Override
353	public boolean onCreateOptionsMenu(Menu menu) {
354		getMenuInflater().inflate(R.menu.verify_otr, menu);
355		if (mConversation != null && mConversation.isOtrFingerprintVerified()) {
356			MenuItem manuallyVerifyItem = menu.findItem(R.id.manually_verify);
357			manuallyVerifyItem.setVisible(false);
358		}
359		return true;
360	}
361
362	@Override
363	public boolean onOptionsItemSelected(MenuItem menuItem) {
364		if (menuItem.getItemId() == R.id.manually_verify) {
365			showManuallyVerifyDialog();
366			return true;
367		} else {
368			return super.onOptionsItemSelected(menuItem);
369		}
370	}
371
372	private void showManuallyVerifyDialog() {
373		AlertDialog.Builder builder = new AlertDialog.Builder(this);
374		builder.setTitle(R.string.manually_verify);
375		builder.setMessage(R.string.are_you_sure_verify_fingerprint);
376		builder.setNegativeButton(R.string.cancel, null);
377		builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
378		builder.create().show();
379	}
380
381	@Override
382	protected String getShareableUri() {
383		if (mAccount!=null) {
384			return mAccount.getShareableUri();
385		} else {
386			return "";
387		}
388	}
389
390	public void onConversationUpdate() {
391		runOnUiThread(new Runnable() {
392			@Override
393			public void run() {
394				updateView();
395			}
396		});
397	}
398}