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