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}