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.View;
9import android.widget.Button;
10import android.widget.EditText;
11import android.widget.ImageView;
12import android.widget.LinearLayout;
13import android.widget.RelativeLayout;
14import android.widget.TextView;
15import android.widget.Toast;
16
17import com.google.zxing.integration.android.IntentIntegrator;
18import com.google.zxing.integration.android.IntentResult;
19
20import net.java.otr4j.OtrException;
21import net.java.otr4j.session.Session;
22
23import eu.siacs.conversations.R;
24import eu.siacs.conversations.entities.Account;
25import eu.siacs.conversations.entities.Contact;
26import eu.siacs.conversations.entities.Conversation;
27import eu.siacs.conversations.services.XmppConnectionService;
28import eu.siacs.conversations.utils.CryptoHelper;
29import eu.siacs.conversations.utils.XmppUri;
30import eu.siacs.conversations.xmpp.jid.InvalidJidException;
31import eu.siacs.conversations.xmpp.jid.Jid;
32
33public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
34
35 public static final String ACTION_VERIFY_CONTACT = "verify_contact";
36 public static final int MODE_SCAN_FINGERPRINT = - 0x0502;
37 public static final int MODE_ASK_QUESTION = 0x0503;
38 public static final int MODE_ANSWER_QUESTION = 0x0504;
39 public static final int MODE_MANUAL_VERIFICATION = 0x0505;
40
41 private LinearLayout mManualVerificationArea;
42 private LinearLayout mSmpVerificationArea;
43 private TextView mRemoteFingerprint;
44 private TextView mYourFingerprint;
45 private TextView mVerificationExplain;
46 private TextView mStatusMessage;
47 private TextView mSharedSecretHint;
48 private EditText mSharedSecretHintEditable;
49 private EditText mSharedSecretSecret;
50 private Button mLeftButton;
51 private Button mRightButton;
52 private Account mAccount;
53 private Conversation mConversation;
54 private int mode = MODE_MANUAL_VERIFICATION;
55 private XmppUri mPendingUri = null;
56
57 private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
58
59 @Override
60 public void onClick(DialogInterface dialogInterface, int click) {
61 mConversation.verifyOtrFingerprint();
62 xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
63 Toast.makeText(VerifyOTRActivity.this,R.string.verified,Toast.LENGTH_SHORT).show();
64 finish();
65 }
66 };
67
68 private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
69 @Override
70 public void onClick(final View view) {
71 if (isAccountOnline()) {
72 final String question = mSharedSecretHintEditable.getText().toString();
73 final String secret = mSharedSecretSecret.getText().toString();
74 if (question.trim().isEmpty()) {
75 mSharedSecretHintEditable.requestFocus();
76 mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty));
77 } else if (secret.trim().isEmpty()) {
78 mSharedSecretSecret.requestFocus();
79 mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty));
80 } else {
81 mSharedSecretSecret.setError(null);
82 mSharedSecretHintEditable.setError(null);
83 initSmp(question, secret);
84 updateView();
85 }
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 = mSharedSecretHintEditable.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 protected boolean initSmp(final String question, final String secret) {
128 final Session session = mConversation.getOtrSession();
129 if (session!=null) {
130 try {
131 session.initSmp(question, secret);
132 mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
133 mConversation.smp().secret = secret;
134 mConversation.smp().hint = question;
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 boolean 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 return true;
183 } else {
184 Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();
185 return false;
186 }
187 }
188
189 protected boolean isAccountOnline() {
190 if (this.mAccount.getStatus() != Account.State.ONLINE) {
191 Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
192 return false;
193 } else {
194 return true;
195 }
196 }
197
198 protected boolean handleIntent(Intent intent) {
199 if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
200 try {
201 this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account")));
202 } catch (final InvalidJidException ignored) {
203 return false;
204 }
205 try {
206 this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact")));
207 if (this.mConversation == null) {
208 return false;
209 }
210 } catch (final InvalidJidException ignored) {
211 return false;
212 }
213 this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION);
214 if (this.mode == MODE_SCAN_FINGERPRINT) {
215 new IntentIntegrator(this).initiateScan();
216 return false;
217 }
218 return true;
219 } else {
220 return false;
221 }
222 }
223
224 @Override
225 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
226 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
227 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
228 if (scanResult != null && scanResult.getFormatName() != null) {
229 String data = scanResult.getContents();
230 XmppUri uri = new XmppUri(data);
231 if (xmppConnectionServiceBound) {
232 verifyWithUri(uri);
233 finish();
234 } else {
235 this.mPendingUri = uri;
236 }
237 } else {
238 finish();
239 }
240 }
241 super.onActivityResult(requestCode, requestCode, intent);
242 }
243
244 @Override
245 protected void onBackendConnected() {
246 if (handleIntent(getIntent())) {
247 updateView();
248 } else if (mPendingUri!=null) {
249 verifyWithUri(mPendingUri);
250 finish();
251 mPendingUri = null;
252 }
253 setIntent(null);
254 }
255
256 protected void updateView() {
257 if (this.mConversation.hasValidOtrSession()) {
258 final ActionBar actionBar = getActionBar();
259 this.mVerificationExplain.setText(R.string.no_otr_session_found);
260 invalidateOptionsMenu();
261 switch(this.mode) {
262 case MODE_ASK_QUESTION:
263 if (actionBar != null ) {
264 actionBar.setTitle(R.string.ask_question);
265 }
266 this.updateViewAskQuestion();
267 break;
268 case MODE_ANSWER_QUESTION:
269 if (actionBar != null ) {
270 actionBar.setTitle(R.string.smp_requested);
271 }
272 this.updateViewAnswerQuestion();
273 break;
274 case MODE_MANUAL_VERIFICATION:
275 default:
276 if (actionBar != null ) {
277 actionBar.setTitle(R.string.manually_verify);
278 }
279 this.updateViewManualVerification();
280 break;
281 }
282 } else {
283 this.mManualVerificationArea.setVisibility(View.GONE);
284 this.mSmpVerificationArea.setVisibility(View.GONE);
285 }
286 }
287
288 protected void updateViewManualVerification() {
289 this.mVerificationExplain.setText(R.string.manual_verification_explanation);
290 this.mManualVerificationArea.setVisibility(View.VISIBLE);
291 this.mSmpVerificationArea.setVisibility(View.GONE);
292 this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
293 this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint()));
294 if (this.mConversation.isOtrFingerprintVerified()) {
295 deactivateButton(this.mRightButton,R.string.verified);
296 activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
297 } else {
298 activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
299 activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() {
300 @Override
301 public void onClick(View view) {
302 showManuallyVerifyDialog();
303 }
304 });
305 }
306 }
307
308 protected void updateViewAskQuestion() {
309 this.mManualVerificationArea.setVisibility(View.GONE);
310 this.mSmpVerificationArea.setVisibility(View.VISIBLE);
311 this.mVerificationExplain.setText(R.string.smp_explain_question);
312 final int smpStatus = this.mConversation.smp().status;
313 switch (smpStatus) {
314 case Conversation.Smp.STATUS_WE_REQUESTED:
315 this.mStatusMessage.setVisibility(View.GONE);
316 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
317 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
318 this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint);
319 this.mSharedSecretSecret.setText(this.mConversation.smp().secret);
320 this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener);
321 this.deactivateButton(this.mRightButton, R.string.in_progress);
322 break;
323 case Conversation.Smp.STATUS_FAILED:
324 this.mStatusMessage.setVisibility(View.GONE);
325 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
326 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
327 this.mSharedSecretSecret.requestFocus();
328 this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
329 this.deactivateButton(this.mLeftButton, R.string.cancel);
330 this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener);
331 break;
332 case Conversation.Smp.STATUS_VERIFIED:
333 this.mSharedSecretHintEditable.setText("");
334 this.mSharedSecretHintEditable.setVisibility(View.GONE);
335 this.mSharedSecretSecret.setText("");
336 this.mSharedSecretSecret.setVisibility(View.GONE);
337 this.mStatusMessage.setVisibility(View.VISIBLE);
338 this.deactivateButton(this.mLeftButton, R.string.cancel);
339 this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
340 break;
341 default:
342 this.mStatusMessage.setVisibility(View.GONE);
343 this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
344 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
345 this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
346 this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener);
347 break;
348 }
349 }
350
351 protected void updateViewAnswerQuestion() {
352 this.mManualVerificationArea.setVisibility(View.GONE);
353 this.mSmpVerificationArea.setVisibility(View.VISIBLE);
354 this.mVerificationExplain.setText(R.string.smp_explain_answer);
355 this.mSharedSecretHintEditable.setVisibility(View.GONE);
356 this.mSharedSecretHint.setVisibility(View.VISIBLE);
357 this.deactivateButton(this.mLeftButton, R.string.cancel);
358 final int smpStatus = this.mConversation.smp().status;
359 switch (smpStatus) {
360 case Conversation.Smp.STATUS_CONTACT_REQUESTED:
361 this.mStatusMessage.setVisibility(View.GONE);
362 this.mSharedSecretHint.setText(this.mConversation.smp().hint);
363 this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener);
364 break;
365 case Conversation.Smp.STATUS_VERIFIED:
366 this.mSharedSecretHintEditable.setText("");
367 this.mSharedSecretHintEditable.setVisibility(View.GONE);
368 this.mSharedSecretHint.setVisibility(View.GONE);
369 this.mSharedSecretSecret.setText("");
370 this.mSharedSecretSecret.setVisibility(View.GONE);
371 this.mStatusMessage.setVisibility(View.VISIBLE);
372 this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
373 break;
374 case Conversation.Smp.STATUS_FAILED:
375 default:
376 this.mSharedSecretSecret.requestFocus();
377 this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
378 this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener);
379 break;
380 }
381 }
382
383 protected void activateButton(Button button, int text, View.OnClickListener listener) {
384 button.setEnabled(true);
385 button.setTextColor(getPrimaryTextColor());
386 button.setText(text);
387 button.setOnClickListener(listener);
388 }
389
390 protected void deactivateButton(Button button, int text) {
391 button.setEnabled(false);
392 button.setTextColor(getSecondaryTextColor());
393 button.setText(text);
394 button.setOnClickListener(null);
395 }
396
397 @Override
398 protected void onCreate(Bundle savedInstanceState) {
399 super.onCreate(savedInstanceState);
400 setContentView(R.layout.activity_verify_otr);
401 this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
402 this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
403 this.mLeftButton = (Button) findViewById(R.id.left_button);
404 this.mRightButton = (Button) findViewById(R.id.right_button);
405 this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation);
406 this.mStatusMessage = (TextView) findViewById(R.id.status_message);
407 this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
408 this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable);
409 this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint);
410 this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area);
411 this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area);
412 }
413
414 private void showManuallyVerifyDialog() {
415 AlertDialog.Builder builder = new AlertDialog.Builder(this);
416 builder.setTitle(R.string.manually_verify);
417 builder.setMessage(R.string.are_you_sure_verify_fingerprint);
418 builder.setNegativeButton(R.string.cancel, null);
419 builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
420 builder.create().show();
421 }
422
423 @Override
424 protected String getShareableUri() {
425 if (mAccount!=null) {
426 return mAccount.getShareableUri();
427 } else {
428 return "";
429 }
430 }
431
432 public void onConversationUpdate() {
433 runOnUiThread(new Runnable() {
434 @Override
435 public void run() {
436 updateView();
437 }
438 });
439 }
440}