1package eu.siacs.conversations.ui;
2
3import android.app.AlertDialog;
4import android.content.DialogInterface;
5import android.content.Intent;
6import android.os.Bundle;
7import android.view.Menu;
8import android.view.MenuItem;
9import android.view.View;
10import android.widget.Button;
11import android.widget.EditText;
12import android.widget.RelativeLayout;
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
36 private RelativeLayout mVerificationAreaOne;
37 private RelativeLayout mVerificationAreaTwo;
38 private TextView mErrorNoSession;
39 private TextView mRemoteJid;
40 private TextView mRemoteFingerprint;
41 private TextView mYourFingerprint;
42 private EditText mSharedSecretHint;
43 private EditText mSharedSecretSecret;
44 private Button mButtonScanQrCode;
45 private Button mButtonShowQrCode;
46 private Button mButtonSharedSecretPositive;
47 private Button mButtonSharedSecretNegative;
48 private TextView mStatusMessage;
49 private Account mAccount;
50 private Conversation mConversation;
51
52 private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
53
54 @Override
55 public void onClick(DialogInterface dialogInterface, int click) {
56 mConversation.verifyOtrFingerprint();
57 updateView();
58 xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
59 }
60 };
61
62 private View.OnClickListener mShowQrCodeListener = new View.OnClickListener() {
63 @Override
64 public void onClick(final View view) {
65 showQrCode();
66 }
67 };
68
69 private View.OnClickListener mScanQrCodeListener = new View.OnClickListener() {
70
71 @Override
72 public void onClick(View view) {
73 new IntentIntegrator(VerifyOTRActivity.this).initiateScan();
74 }
75
76 };
77
78 private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
79 @Override
80 public void onClick(final View view) {
81 if (isAccountOnline()) {
82 final String question = mSharedSecretHint.getText().toString();
83 final String secret = mSharedSecretSecret.getText().toString();
84 initSmp(question, secret);
85 updateView();
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 = mSharedSecretHint.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 private XmppUri mPendingUri = null;
128
129 protected boolean initSmp(final String question, final String secret) {
130 final Session session = mConversation.getOtrSession();
131 if (session!=null) {
132 try {
133 session.initSmp(question, secret);
134 mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
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 void 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 } else {
183 Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();
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.getAction().equals(ACTION_VERIFY_CONTACT)) {
198 try {
199 this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account")));
200 } catch (final InvalidJidException ignored) {
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 return true;
212 } else {
213 return false;
214 }
215 }
216
217 @Override
218 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
219 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
220 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
221 if (scanResult != null && scanResult.getFormatName() != null) {
222 String data = scanResult.getContents();
223 XmppUri uri = new XmppUri(data);
224 if (xmppConnectionServiceBound) {
225 verifyWithUri(uri);
226 } else {
227 this.mPendingUri = uri;
228 }
229 }
230 }
231 super.onActivityResult(requestCode, requestCode, intent);
232 }
233
234 @Override
235 protected void onBackendConnected() {
236 if (handleIntent(getIntent())) {
237 if (mPendingUri!=null) {
238 verifyWithUri(mPendingUri);
239 mPendingUri = null;
240 }
241 updateView();
242 }
243 }
244
245 protected void updateView() {
246 if (this.mConversation.hasValidOtrSession()) {
247 invalidateOptionsMenu();
248 this.mVerificationAreaOne.setVisibility(View.VISIBLE);
249 this.mVerificationAreaTwo.setVisibility(View.VISIBLE);
250 this.mErrorNoSession.setVisibility(View.GONE);
251 this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
252 this.mRemoteFingerprint.setText(this.mConversation.getOtrFingerprint());
253 this.mRemoteJid.setText(this.mConversation.getContact().getJid().toBareJid().toString());
254 Conversation.Smp smp = mConversation.smp();
255 Session session = mConversation.getOtrSession();
256 if (mConversation.isOtrFingerprintVerified()) {
257 deactivateButton(mButtonScanQrCode, R.string.verified);
258 } else {
259 activateButton(mButtonScanQrCode, R.string.scan_qr_code, mScanQrCodeListener);
260 }
261 if (smp.status == Conversation.Smp.STATUS_NONE) {
262 activateButton(mButtonSharedSecretPositive, R.string.create, mCreateSharedSecretListener);
263 deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
264 this.mSharedSecretHint.setFocusableInTouchMode(true);
265 this.mSharedSecretSecret.setFocusableInTouchMode(true);
266 this.mSharedSecretSecret.setText("");
267 this.mSharedSecretHint.setText("");
268 this.mSharedSecretHint.setVisibility(View.VISIBLE);
269 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
270 this.mStatusMessage.setVisibility(View.GONE);
271 } else if (smp.status == Conversation.Smp.STATUS_CONTACT_REQUESTED) {
272 this.mSharedSecretHint.setFocusable(false);
273 this.mSharedSecretHint.setText(smp.hint);
274 this.mSharedSecretSecret.setFocusableInTouchMode(true);
275 this.mSharedSecretHint.setVisibility(View.VISIBLE);
276 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
277 this.mStatusMessage.setVisibility(View.GONE);
278 deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
279 activateButton(mButtonSharedSecretPositive, R.string.respond, mRespondSharedSecretListener);
280 } else if (smp.status == Conversation.Smp.STATUS_FAILED) {
281 activateButton(mButtonSharedSecretNegative, R.string.cancel, mFinishListener);
282 activateButton(mButtonSharedSecretPositive, R.string.try_again, mRetrySharedSecretListener);
283 this.mSharedSecretHint.setVisibility(View.GONE);
284 this.mSharedSecretSecret.setVisibility(View.GONE);
285 this.mStatusMessage.setVisibility(View.VISIBLE);
286 this.mStatusMessage.setText(R.string.secrets_do_not_match);
287 this.mStatusMessage.setTextColor(getWarningTextColor());
288 } else if (smp.status == Conversation.Smp.STATUS_FINISHED) {
289 this.mSharedSecretHint.setText("");
290 this.mSharedSecretHint.setVisibility(View.GONE);
291 this.mSharedSecretSecret.setText("");
292 this.mSharedSecretSecret.setVisibility(View.GONE);
293 this.mStatusMessage.setVisibility(View.VISIBLE);
294 this.mStatusMessage.setTextColor(getPrimaryColor());
295 deactivateButton(mButtonSharedSecretNegative, R.string.cancel);
296 if (mConversation.isOtrFingerprintVerified()) {
297 activateButton(mButtonSharedSecretPositive, R.string.finish, mFinishListener);
298 this.mStatusMessage.setText(R.string.verified);
299 } else {
300 activateButton(mButtonSharedSecretPositive,R.string.reset,mRetrySharedSecretListener);
301 this.mStatusMessage.setText(R.string.secret_accepted);
302 }
303 } else if (session != null && session.isSmpInProgress()) {
304 deactivateButton(mButtonSharedSecretPositive, R.string.in_progress);
305 activateButton(mButtonSharedSecretNegative, R.string.cancel, mCancelSharedSecretListener);
306 this.mSharedSecretHint.setVisibility(View.VISIBLE);
307 this.mSharedSecretSecret.setVisibility(View.VISIBLE);
308 this.mSharedSecretHint.setFocusable(false);
309 this.mSharedSecretSecret.setFocusable(false);
310 }
311 } else {
312 this.mVerificationAreaOne.setVisibility(View.GONE);
313 this.mVerificationAreaTwo.setVisibility(View.GONE);
314 this.mErrorNoSession.setVisibility(View.VISIBLE);
315 }
316 }
317
318 protected void activateButton(Button button, int text, View.OnClickListener listener) {
319 button.setEnabled(true);
320 button.setTextColor(getPrimaryTextColor());
321 button.setText(text);
322 button.setOnClickListener(listener);
323 }
324
325 protected void deactivateButton(Button button, int text) {
326 button.setEnabled(false);
327 button.setTextColor(getSecondaryTextColor());
328 button.setText(text);
329 button.setOnClickListener(null);
330 }
331
332 @Override
333 protected void onCreate(Bundle savedInstanceState) {
334 super.onCreate(savedInstanceState);
335 setContentView(R.layout.activity_verify_otr);
336 this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
337 this.mRemoteJid = (TextView) findViewById(R.id.remote_jid);
338 this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
339 this.mButtonSharedSecretNegative = (Button) findViewById(R.id.button_shared_secret_negative);
340 this.mButtonSharedSecretPositive = (Button) findViewById(R.id.button_shared_secret_positive);
341 this.mButtonScanQrCode = (Button) findViewById(R.id.button_scan_qr_code);
342 this.mButtonShowQrCode = (Button) findViewById(R.id.button_show_qr_code);
343 this.mButtonShowQrCode.setOnClickListener(this.mShowQrCodeListener);
344 this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
345 this.mSharedSecretHint = (EditText) findViewById(R.id.shared_secret_hint);
346 this.mStatusMessage= (TextView) findViewById(R.id.status_message);
347 this.mVerificationAreaOne = (RelativeLayout) findViewById(R.id.verification_area_one);
348 this.mVerificationAreaTwo = (RelativeLayout) findViewById(R.id.verification_area_two);
349 this.mErrorNoSession = (TextView) findViewById(R.id.error_no_session);
350 }
351
352 @Override
353 public boolean onCreateOptionsMenu(Menu menu) {
354 getMenuInflater().inflate(R.menu.verify_otr, menu);
355 if (mConversation != null && mConversation.isOtrFingerprintVerified()) {
356 MenuItem manuallyVerifyItem = menu.findItem(R.id.manually_verify);
357 manuallyVerifyItem.setVisible(false);
358 }
359 return true;
360 }
361
362 @Override
363 public boolean onOptionsItemSelected(MenuItem menuItem) {
364 if (menuItem.getItemId() == R.id.manually_verify) {
365 showManuallyVerifyDialog();
366 return true;
367 } else {
368 return super.onOptionsItemSelected(menuItem);
369 }
370 }
371
372 private void showManuallyVerifyDialog() {
373 AlertDialog.Builder builder = new AlertDialog.Builder(this);
374 builder.setTitle(R.string.manually_verify);
375 builder.setMessage(R.string.are_you_sure_verify_fingerprint);
376 builder.setNegativeButton(R.string.cancel, null);
377 builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
378 builder.create().show();
379 }
380
381 @Override
382 protected String getShareableUri() {
383 if (mAccount!=null) {
384 return mAccount.getShareableUri();
385 } else {
386 return "";
387 }
388 }
389
390 public void onConversationUpdate() {
391 runOnUiThread(new Runnable() {
392 @Override
393 public void run() {
394 updateView();
395 }
396 });
397 }
398}