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