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 this.mAccount = extractAccount(intent);
200 if (this.mAccount == null) {
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 this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION);
212 if (this.mode == MODE_SCAN_FINGERPRINT) {
213 new IntentIntegrator(this).initiateScan();
214 return false;
215 }
216 return true;
217 } else {
218 return false;
219 }
220 }
221
222 @Override
223 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
224 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
225 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
226 if (scanResult != null && scanResult.getFormatName() != null) {
227 String data = scanResult.getContents();
228 XmppUri uri = new XmppUri(data);
229 if (xmppConnectionServiceBound) {
230 verifyWithUri(uri);
231 finish();
232 } else {
233 this.mPendingUri = uri;
234 }
235 } else {
236 finish();
237 }
238 }
239 super.onActivityResult(requestCode, requestCode, intent);
240 }
241
242 @Override
243 protected void onBackendConnected() {
244 if (handleIntent(getIntent())) {
245 updateView();
246 } else if (mPendingUri!=null) {
247 verifyWithUri(mPendingUri);
248 finish();
249 mPendingUri = null;
250 }
251 setIntent(null);
252 }
253
254 protected void updateView() {
255 if (this.mConversation != null && this.mConversation.hasValidOtrSession()) {
256 final ActionBar actionBar = getActionBar();
257 this.mVerificationExplain.setText(R.string.no_otr_session_found);
258 invalidateOptionsMenu();
259 switch(this.mode) {
260 case MODE_ASK_QUESTION:
261 if (actionBar != null ) {
262 actionBar.setTitle(R.string.ask_question);
263 }
264 this.updateViewAskQuestion();
265 break;
266 case MODE_ANSWER_QUESTION:
267 if (actionBar != null ) {
268 actionBar.setTitle(R.string.smp_requested);
269 }
270 this.updateViewAnswerQuestion();
271 break;
272 case MODE_MANUAL_VERIFICATION:
273 default:
274 if (actionBar != null ) {
275 actionBar.setTitle(R.string.manually_verify);
276 }
277 this.updateViewManualVerification();
278 break;
279 }
280 } else {
281 this.mManualVerificationArea.setVisibility(View.GONE);
282 this.mSmpVerificationArea.setVisibility(View.GONE);
283 }
284 }
285
286 protected void updateViewManualVerification() {
287 this.mVerificationExplain.setText(R.string.manual_verification_explanation);
288 this.mManualVerificationArea.setVisibility(View.VISIBLE);
289 this.mSmpVerificationArea.setVisibility(View.GONE);
290 this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
291 this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint()));
292 if (this.mConversation.isOtrFingerprintVerified()) {
293 deactivateButton(this.mRightButton,R.string.verified);
294 activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
295 } else {
296 activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
297 activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() {
298 @Override
299 public void onClick(View view) {
300 showManuallyVerifyDialog();
301 }
302 });
303 }
304 }
305
306 protected void updateViewAskQuestion() {
307 this.mManualVerificationArea.setVisibility(View.GONE);
308 this.mSmpVerificationArea.setVisibility(View.VISIBLE);
309 this.mVerificationExplain.setText(R.string.smp_explain_question);
310 final int smpStatus = this.mConversation.smp().status;
311 switch (smpStatus) {
312 case Conversation.Smp.STATUS_WE_REQUESTED:
313 this.mStatusMessage.setVisibility(View.GONE);
314 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
315 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
316 this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint);
317 this.mSharedSecretSecret.setText(this.mConversation.smp().secret);
318 this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener);
319 this.deactivateButton(this.mRightButton, R.string.in_progress);
320 break;
321 case Conversation.Smp.STATUS_FAILED:
322 this.mStatusMessage.setVisibility(View.GONE);
323 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
324 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
325 this.mSharedSecretSecret.requestFocus();
326 this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
327 this.deactivateButton(this.mLeftButton, R.string.cancel);
328 this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener);
329 break;
330 case Conversation.Smp.STATUS_VERIFIED:
331 this.mSharedSecretHintEditable.setText("");
332 this.mSharedSecretHintEditable.setVisibility(View.GONE);
333 this.mSharedSecretSecret.setText("");
334 this.mSharedSecretSecret.setVisibility(View.GONE);
335 this.mStatusMessage.setVisibility(View.VISIBLE);
336 this.deactivateButton(this.mLeftButton, R.string.cancel);
337 this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
338 break;
339 default:
340 this.mStatusMessage.setVisibility(View.GONE);
341 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
342 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
343 this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
344 this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener);
345 break;
346 }
347 }
348
349 protected void updateViewAnswerQuestion() {
350 this.mManualVerificationArea.setVisibility(View.GONE);
351 this.mSmpVerificationArea.setVisibility(View.VISIBLE);
352 this.mVerificationExplain.setText(R.string.smp_explain_answer);
353 this.mSharedSecretHintEditable.setVisibility(View.GONE);
354 this.mSharedSecretHint.setVisibility(View.VISIBLE);
355 this.deactivateButton(this.mLeftButton, R.string.cancel);
356 final int smpStatus = this.mConversation.smp().status;
357 switch (smpStatus) {
358 case Conversation.Smp.STATUS_CONTACT_REQUESTED:
359 this.mStatusMessage.setVisibility(View.GONE);
360 this.mSharedSecretHint.setText(this.mConversation.smp().hint);
361 this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener);
362 break;
363 case Conversation.Smp.STATUS_VERIFIED:
364 this.mSharedSecretHintEditable.setText("");
365 this.mSharedSecretHintEditable.setVisibility(View.GONE);
366 this.mSharedSecretHint.setVisibility(View.GONE);
367 this.mSharedSecretSecret.setText("");
368 this.mSharedSecretSecret.setVisibility(View.GONE);
369 this.mStatusMessage.setVisibility(View.VISIBLE);
370 this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
371 break;
372 case Conversation.Smp.STATUS_FAILED:
373 default:
374 this.mSharedSecretSecret.requestFocus();
375 this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
376 this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener);
377 break;
378 }
379 }
380
381 protected void activateButton(Button button, int text, View.OnClickListener listener) {
382 button.setEnabled(true);
383 button.setTextColor(getPrimaryTextColor());
384 button.setText(text);
385 button.setOnClickListener(listener);
386 }
387
388 protected void deactivateButton(Button button, int text) {
389 button.setEnabled(false);
390 button.setTextColor(getSecondaryTextColor());
391 button.setText(text);
392 button.setOnClickListener(null);
393 }
394
395 @Override
396 protected void onCreate(Bundle savedInstanceState) {
397 super.onCreate(savedInstanceState);
398 setContentView(R.layout.activity_verify_otr);
399 this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
400 this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
401 this.mLeftButton = (Button) findViewById(R.id.left_button);
402 this.mRightButton = (Button) findViewById(R.id.right_button);
403 this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation);
404 this.mStatusMessage = (TextView) findViewById(R.id.status_message);
405 this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
406 this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable);
407 this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint);
408 this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area);
409 this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area);
410 }
411
412 @Override
413 public boolean onCreateOptionsMenu(final Menu menu) {
414 super.onCreateOptionsMenu(menu);
415 getMenuInflater().inflate(R.menu.verify_otr, menu);
416 return true;
417 }
418
419 private void showManuallyVerifyDialog() {
420 AlertDialog.Builder builder = new AlertDialog.Builder(this);
421 builder.setTitle(R.string.manually_verify);
422 builder.setMessage(R.string.are_you_sure_verify_fingerprint);
423 builder.setNegativeButton(R.string.cancel, null);
424 builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
425 builder.create().show();
426 }
427
428 @Override
429 protected String getShareableUri() {
430 if (mAccount!=null) {
431 return mAccount.getShareableUri();
432 } else {
433 return "";
434 }
435 }
436
437 public void onConversationUpdate() {
438 refreshUi();
439 }
440
441 @Override
442 protected void refreshUiReal() {
443 updateView();
444 }
445}