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