Merge branch 'master' into crash_fixes
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / AuthenticatorActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.ui.activity;
20
21 import java.net.MalformedURLException;
22 import java.net.URL;
23
24 import com.owncloud.android.AccountUtils;
25 import com.owncloud.android.authenticator.AccountAuthenticator;
26 import com.owncloud.android.authenticator.AuthenticationRunnable;
27 import com.owncloud.android.authenticator.OnAuthenticationResultListener;
28 import com.owncloud.android.authenticator.OnConnectCheckListener;
29 import com.owncloud.android.ui.dialog.SslValidatorDialog;
30 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
31 import com.owncloud.android.network.OwnCloudClientUtils;
32 import com.owncloud.android.operations.ConnectionCheckOperation;
33 import com.owncloud.android.operations.OnRemoteOperationListener;
34 import com.owncloud.android.operations.RemoteOperation;
35 import com.owncloud.android.operations.RemoteOperationResult;
36
37 import android.accounts.Account;
38 import android.accounts.AccountAuthenticatorActivity;
39 import android.accounts.AccountManager;
40 import android.app.AlertDialog;
41 import android.app.Dialog;
42 import android.app.ProgressDialog;
43 import android.content.ContentResolver;
44 import android.content.DialogInterface;
45 import android.content.Intent;
46 import android.content.SharedPreferences;
47 import android.net.Uri;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.preference.PreferenceManager;
51 import android.text.InputType;
52 import android.util.Log;
53 import android.view.View;
54 import android.view.View.OnClickListener;
55 import android.view.View.OnFocusChangeListener;
56 import android.view.Window;
57 import android.widget.Button;
58 import android.widget.EditText;
59 import android.widget.ImageView;
60 import android.widget.TextView;
61 import com.owncloud.android.R;
62
63 import eu.alefzero.webdav.WebdavClient;
64
65 /**
66 * This Activity is used to add an ownCloud account to the App
67 *
68 * @author Bartek Przybylski
69 *
70 */
71 public class AuthenticatorActivity extends AccountAuthenticatorActivity
72 implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener,
73 OnFocusChangeListener, OnClickListener {
74
75 private static final int DIALOG_LOGIN_PROGRESS = 0;
76 private static final int DIALOG_SSL_VALIDATOR = 1;
77 private static final int DIALOG_CERT_NOT_SAVED = 2;
78
79 private static final String TAG = "AuthActivity";
80
81 private Thread mAuthThread;
82 private AuthenticationRunnable mAuthRunnable;
83 //private ConnectionCheckerRunnable mConnChkRunnable = null;
84 private ConnectionCheckOperation mConnChkRunnable;
85 private final Handler mHandler = new Handler();
86 private String mBaseUrl;
87
88 private static final String STATUS_TEXT = "STATUS_TEXT";
89 private static final String STATUS_ICON = "STATUS_ICON";
90 private static final String STATUS_CORRECT = "STATUS_CORRECT";
91 private static final String IS_SSL_CONN = "IS_SSL_CONN";
92 private int mStatusText, mStatusIcon;
93 private boolean mStatusCorrect, mIsSslConn;
94 private RemoteOperationResult mLastSslUntrustedServerResult;
95
96 public static final String PARAM_USERNAME = "param_Username";
97 public static final String PARAM_HOSTNAME = "param_Hostname";
98
99 @Override
100 protected void onCreate(Bundle savedInstanceState) {
101 super.onCreate(savedInstanceState);
102 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
103 setContentView(R.layout.account_setup);
104 ImageView iv = (ImageView) findViewById(R.id.refreshButton);
105 ImageView iv2 = (ImageView) findViewById(R.id.viewPassword);
106 TextView tv = (TextView) findViewById(R.id.host_URL);
107 TextView tv2 = (TextView) findViewById(R.id.account_password);
108
109 if (savedInstanceState != null) {
110 mStatusIcon = savedInstanceState.getInt(STATUS_ICON);
111 mStatusText = savedInstanceState.getInt(STATUS_TEXT);
112 mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT);
113 mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN);
114 setResultIconAndText(mStatusIcon, mStatusText);
115 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
116 if (!mStatusCorrect)
117 iv.setVisibility(View.VISIBLE);
118 else
119 iv.setVisibility(View.INVISIBLE);
120
121 } else {
122 mStatusText = mStatusIcon = 0;
123 mStatusCorrect = false;
124 mIsSslConn = false;
125 }
126 iv.setOnClickListener(this);
127 iv2.setOnClickListener(this);
128 tv.setOnFocusChangeListener(this);
129 tv2.setOnFocusChangeListener(this);
130
131 Button b = (Button) findViewById(R.id.account_register);
132 if (b != null) {
133 b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));
134 }
135 }
136
137 @Override
138 protected void onSaveInstanceState(Bundle outState) {
139 outState.putInt(STATUS_ICON, mStatusIcon);
140 outState.putInt(STATUS_TEXT, mStatusText);
141 outState.putBoolean(STATUS_CORRECT, mStatusCorrect);
142 super.onSaveInstanceState(outState);
143 }
144
145 @Override
146 protected Dialog onCreateDialog(int id) {
147 Dialog dialog = null;
148 switch (id) {
149 case DIALOG_LOGIN_PROGRESS: {
150 ProgressDialog working_dialog = new ProgressDialog(this);
151 working_dialog.setMessage(getResources().getString(
152 R.string.auth_trying_to_login));
153 working_dialog.setIndeterminate(true);
154 working_dialog.setCancelable(true);
155 working_dialog
156 .setOnCancelListener(new DialogInterface.OnCancelListener() {
157 @Override
158 public void onCancel(DialogInterface dialog) {
159 Log.i(TAG, "Login canceled");
160 if (mAuthThread != null) {
161 mAuthThread.interrupt();
162 finish();
163 }
164 }
165 });
166 dialog = working_dialog;
167 break;
168 }
169 case DIALOG_SSL_VALIDATOR: {
170 dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);
171 break;
172 }
173 case DIALOG_CERT_NOT_SAVED: {
174 AlertDialog.Builder builder = new AlertDialog.Builder(this);
175 builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
176 builder.setCancelable(false);
177 builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
178 @Override
179 public void onClick(DialogInterface dialog, int which) {
180 dialog.dismiss();
181 };
182 });
183 dialog = builder.create();
184 break;
185 }
186 default:
187 Log.e(TAG, "Incorrect dialog called with id = " + id);
188 }
189 return dialog;
190 }
191
192 @Override
193 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
194 switch (id) {
195 case DIALOG_LOGIN_PROGRESS:
196 case DIALOG_CERT_NOT_SAVED:
197 break;
198 case DIALOG_SSL_VALIDATOR: {
199 ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);
200 break;
201 }
202 default:
203 Log.e(TAG, "Incorrect dialog called with id = " + id);
204 }
205 }
206
207 public void onAuthenticationResult(boolean success, String message) {
208 if (success) {
209 TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password);
210
211 URL url;
212 try {
213 url = new URL(message);
214 } catch (MalformedURLException e) {
215 // should never happen
216 Log.e(getClass().getName(), "Malformed URL: " + message);
217 return;
218 }
219
220 String username = username_text.getText().toString().trim();
221 String accountName = username + "@" + url.getHost();
222 if (url.getPort() >= 0) {
223 accountName += ":" + url.getPort();
224 }
225 Account account = new Account(accountName,
226 AccountAuthenticator.ACCOUNT_TYPE);
227 AccountManager accManager = AccountManager.get(this);
228 accManager.addAccountExplicitly(account, password_text.getText()
229 .toString(), null);
230
231 // Add this account as default in the preferences, if there is none
232 // already
233 Account defaultAccount = AccountUtils
234 .getCurrentOwnCloudAccount(this);
235 if (defaultAccount == null) {
236 SharedPreferences.Editor editor = PreferenceManager
237 .getDefaultSharedPreferences(this).edit();
238 editor.putString("select_oc_account", accountName);
239 editor.commit();
240 }
241
242 final Intent intent = new Intent();
243 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
244 AccountAuthenticator.ACCOUNT_TYPE);
245 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
246 intent.putExtra(AccountManager.KEY_AUTHTOKEN,
247 AccountAuthenticator.ACCOUNT_TYPE);
248 intent.putExtra(AccountManager.KEY_USERDATA, username);
249
250 accManager.setUserData(account,
251 AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable
252 .getDiscoveredVersion().toString());
253
254 accManager.setUserData(account,
255 AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
256
257 setAccountAuthenticatorResult(intent.getExtras());
258 setResult(RESULT_OK, intent);
259 Bundle bundle = new Bundle();
260 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
261 //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI,
262 // bundle);
263 ContentResolver.requestSync(account, "org.owncloud", bundle);
264
265 /*
266 * if
267 * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion
268 * .owncloud_v2) >= 0) { Intent i = new Intent(this,
269 * ExtensionsAvailableActivity.class); startActivity(i); }
270 */
271
272 finish();
273 } else {
274 try {
275 dismissDialog(DIALOG_LOGIN_PROGRESS);
276 } catch (IllegalArgumentException e) {
277 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
278 }
279 TextView tv = (TextView) findViewById(R.id.account_username);
280 tv.setError(message + " "); // the extra spaces are a workaround for an ugly bug:
281 // 1. insert wrong credentials and connect
282 // 2. put the focus on the user name field with using hardware controls (don't touch the screen); the error is shown UNDER the field
283 // 3. touch the user name field; the software keyboard appears; the error popup is moved OVER the field and SHRINKED in width, losing the last word
284 // Seen, at least, in Android 2.x devices
285 }
286 }
287 public void onCancelClick(View view) {
288 setResult(RESULT_CANCELED);
289 finish();
290 }
291
292 public void onOkClick(View view) {
293 String prefix = "";
294 String url = ((TextView) findViewById(R.id.host_URL)).getText()
295 .toString().trim();
296 if (mIsSslConn) {
297 prefix = "https://";
298 } else {
299 prefix = "http://";
300 }
301 if (url.toLowerCase().startsWith("http://")
302 || url.toLowerCase().startsWith("https://")) {
303 prefix = "";
304 }
305 continueConnection(prefix);
306 }
307
308 public void onRegisterClick(View view) {
309 Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));
310 setResult(RESULT_CANCELED);
311 startActivity(register);
312 }
313
314 private void continueConnection(String prefix) {
315 String url = ((TextView) findViewById(R.id.host_URL)).getText()
316 .toString().trim();
317 String username = ((TextView) findViewById(R.id.account_username))
318 .getText().toString();
319 String password = ((TextView) findViewById(R.id.account_password))
320 .getText().toString();
321 if (url.endsWith("/"))
322 url = url.substring(0, url.length() - 1);
323
324 URL uri = null;
325 String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable
326 .getDiscoveredVersion());
327
328 if (webdav_path == null) {
329 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
330 return;
331 }
332
333 try {
334 mBaseUrl = prefix + url;
335 String url_str = prefix + url + webdav_path;
336 uri = new URL(url_str);
337 } catch (MalformedURLException e) {
338 // should never happen
339 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
340 return;
341 }
342
343 showDialog(DIALOG_LOGIN_PROGRESS);
344 mAuthRunnable = new AuthenticationRunnable(uri, username, password, this);
345 mAuthRunnable.setOnAuthenticationResultListener(this, mHandler);
346 mAuthThread = new Thread(mAuthRunnable);
347 mAuthThread.start();
348 }
349
350 @Override
351 public void onConnectionCheckResult(ResultType type) {
352 mStatusText = mStatusIcon = 0;
353 mStatusCorrect = false;
354 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
355 .toString().trim().toLowerCase();
356
357 switch (type) {
358 case OK_SSL:
359 mIsSslConn = true;
360 mStatusIcon = android.R.drawable.ic_secure;
361 mStatusText = R.string.auth_secure_connection;
362 mStatusCorrect = true;
363 break;
364 case OK_NO_SSL:
365 mIsSslConn = false;
366 mStatusCorrect = true;
367 if (t_url.startsWith("http://") ) {
368 mStatusText = R.string.auth_connection_established;
369 mStatusIcon = R.drawable.ic_ok;
370 } else {
371 mStatusText = R.string.auth_nossl_plain_ok_title;
372 mStatusIcon = android.R.drawable.ic_partial_secure;
373 }
374 break;
375 case BAD_OC_VERSION:
376 mStatusIcon = R.drawable.common_error;
377 mStatusText = R.string.auth_bad_oc_version_title;
378 break;
379 case WRONG_CONNECTION:
380 mStatusIcon = R.drawable.common_error;
381 mStatusText = R.string.auth_wrong_connection_title;
382 break;
383 case TIMEOUT:
384 mStatusIcon = R.drawable.common_error;
385 mStatusText = R.string.auth_timeout_title;
386 break;
387 case INCORRECT_ADDRESS:
388 mStatusIcon = R.drawable.common_error;
389 mStatusText = R.string.auth_incorrect_address_title;
390 break;
391 case SSL_UNVERIFIED_SERVER:
392 mStatusIcon = R.drawable.common_error;
393 mStatusText = R.string.auth_ssl_unverified_server_title;
394 break;
395 case SSL_INIT_ERROR:
396 mStatusIcon = R.drawable.common_error;
397 mStatusText = R.string.auth_ssl_general_error_title;
398 break;
399 case HOST_NOT_AVAILABLE:
400 mStatusIcon = R.drawable.common_error;
401 mStatusText = R.string.auth_unknown_host_title;
402 break;
403 case NO_NETWORK_CONNECTION:
404 mStatusIcon = R.drawable.no_network;
405 mStatusText = R.string.auth_no_net_conn_title;
406 break;
407 case INSTANCE_NOT_CONFIGURED:
408 mStatusIcon = R.drawable.common_error;
409 mStatusText = R.string.auth_not_configured_title;
410 break;
411 case UNKNOWN_ERROR:
412 mStatusIcon = R.drawable.common_error;
413 mStatusText = R.string.auth_unknown_error_title;
414 break;
415 case FILE_NOT_FOUND:
416 mStatusIcon = R.drawable.common_error;
417 mStatusText = R.string.auth_incorrect_path_title;
418 break;
419 default:
420 Log.e(TAG, "Incorrect connection checker result type: " + type);
421 }
422 setResultIconAndText(mStatusIcon, mStatusText);
423 if (!mStatusCorrect)
424 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
425 else
426 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
427 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
428 }
429
430 @Override
431 public void onFocusChange(View view, boolean hasFocus) {
432 if (view.getId() == R.id.host_URL) {
433 if (!hasFocus) {
434 TextView tv = ((TextView) findViewById(R.id.host_URL));
435 String uri = tv.getText().toString().trim();
436 if (uri.length() != 0) {
437 setResultIconAndText(R.drawable.progress_small,
438 R.string.auth_testing_connection);
439 //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
440 mConnChkRunnable = new ConnectionCheckOperation(uri, this);
441 //mConnChkRunnable.setListener(this, mHandler);
442 //mAuthThread = new Thread(mConnChkRunnable);
443 //mAuthThread.start();
444 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this);
445 mAuthThread = mConnChkRunnable.execute(client, this, mHandler);
446 } else {
447 findViewById(R.id.refreshButton).setVisibility(
448 View.INVISIBLE);
449 setResultIconAndText(0, 0);
450 }
451 } else {
452 // avoids that the 'connect' button can be clicked if the test was previously passed
453 findViewById(R.id.buttonOK).setEnabled(false);
454 }
455 } else if (view.getId() == R.id.account_password) {
456 ImageView iv = (ImageView) findViewById(R.id.viewPassword);
457 if (hasFocus) {
458 iv.setVisibility(View.VISIBLE);
459 } else {
460 TextView v = (TextView) findViewById(R.id.account_password);
461 int input_type = InputType.TYPE_CLASS_TEXT
462 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
463 v.setInputType(input_type);
464 iv.setVisibility(View.INVISIBLE);
465 }
466 }
467 }
468
469 private void setResultIconAndText(int drawable_id, int text_id) {
470 ImageView iv = (ImageView) findViewById(R.id.action_indicator);
471 TextView tv = (TextView) findViewById(R.id.status_text);
472
473 if (drawable_id == 0 && text_id == 0) {
474 iv.setVisibility(View.INVISIBLE);
475 tv.setVisibility(View.INVISIBLE);
476 } else {
477 iv.setImageResource(drawable_id);
478 tv.setText(text_id);
479 iv.setVisibility(View.VISIBLE);
480 tv.setVisibility(View.VISIBLE);
481 }
482 }
483
484 @Override
485 public void onClick(View v) {
486 if (v.getId() == R.id.refreshButton) {
487 onFocusChange(findViewById(R.id.host_URL), false);
488 } else if (v.getId() == R.id.viewPassword) {
489 EditText view = (EditText) findViewById(R.id.account_password);
490 int selectionStart = view.getSelectionStart();
491 int selectionEnd = view.getSelectionEnd();
492 int input_type = view.getInputType();
493 if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
494 input_type = InputType.TYPE_CLASS_TEXT
495 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
496 } else {
497 input_type = InputType.TYPE_CLASS_TEXT
498 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
499 }
500 view.setInputType(input_type);
501 view.setSelection(selectionStart, selectionEnd);
502 }
503 }
504
505 @Override
506 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
507 if (operation.equals(mConnChkRunnable)) {
508
509 mStatusText = mStatusIcon = 0;
510 mStatusCorrect = false;
511 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
512 .toString().trim().toLowerCase();
513
514 switch (result.getCode()) {
515 case OK_SSL:
516 mIsSslConn = true;
517 mStatusIcon = android.R.drawable.ic_secure;
518 mStatusText = R.string.auth_secure_connection;
519 mStatusCorrect = true;
520 break;
521
522 case OK_NO_SSL:
523 case OK:
524 mIsSslConn = false;
525 mStatusCorrect = true;
526 if (t_url.startsWith("http://") ) {
527 mStatusText = R.string.auth_connection_established;
528 mStatusIcon = R.drawable.ic_ok;
529 } else {
530 mStatusText = R.string.auth_nossl_plain_ok_title;
531 mStatusIcon = android.R.drawable.ic_partial_secure;
532 }
533 break;
534
535
536 case BAD_OC_VERSION:
537 mStatusIcon = R.drawable.common_error;
538 mStatusText = R.string.auth_bad_oc_version_title;
539 break;
540 case WRONG_CONNECTION:
541 mStatusIcon = R.drawable.common_error;
542 mStatusText = R.string.auth_wrong_connection_title;
543 break;
544 case TIMEOUT:
545 mStatusIcon = R.drawable.common_error;
546 mStatusText = R.string.auth_timeout_title;
547 break;
548 case INCORRECT_ADDRESS:
549 mStatusIcon = R.drawable.common_error;
550 mStatusText = R.string.auth_incorrect_address_title;
551 break;
552
553 case SSL_RECOVERABLE_PEER_UNVERIFIED:
554 mStatusIcon = R.drawable.common_error;
555 mStatusText = R.string.auth_ssl_unverified_server_title;
556 mLastSslUntrustedServerResult = result;
557 showDialog(DIALOG_SSL_VALIDATOR);
558 break;
559
560 case SSL_ERROR:
561 mStatusIcon = R.drawable.common_error;
562 mStatusText = R.string.auth_ssl_general_error_title;
563 break;
564
565 case HOST_NOT_AVAILABLE:
566 mStatusIcon = R.drawable.common_error;
567 mStatusText = R.string.auth_unknown_host_title;
568 break;
569 case NO_NETWORK_CONNECTION:
570 mStatusIcon = R.drawable.no_network;
571 mStatusText = R.string.auth_no_net_conn_title;
572 break;
573 case INSTANCE_NOT_CONFIGURED:
574 mStatusIcon = R.drawable.common_error;
575 mStatusText = R.string.auth_not_configured_title;
576 break;
577 case FILE_NOT_FOUND:
578 mStatusIcon = R.drawable.common_error;
579 mStatusText = R.string.auth_incorrect_path_title;
580 break;
581 case UNHANDLED_HTTP_CODE:
582 case UNKNOWN_ERROR:
583 mStatusIcon = R.drawable.common_error;
584 mStatusText = R.string.auth_unknown_error_title;
585 break;
586 default:
587 Log.e(TAG, "Incorrect connection checker result type: " + result.getHttpCode());
588 }
589 setResultIconAndText(mStatusIcon, mStatusText);
590 if (!mStatusCorrect)
591 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
592 else
593 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
594 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
595 }
596 }
597
598
599 public void onSavedCertificate() {
600 mAuthThread = mConnChkRunnable.retry(this, mHandler);
601 }
602
603 @Override
604 public void onFailedSavingCertificate() {
605 showDialog(DIALOG_CERT_NOT_SAVED);
606 }
607
608 }