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}