Merge branch 'master' into oauth_login
[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 import java.util.HashMap;
24 import java.util.Map;
25
26 import org.json.JSONException;
27 import org.json.JSONObject;
28
29 import com.owncloud.android.AccountUtils;
30 import com.owncloud.android.authenticator.AccountAuthenticator;
31 import com.owncloud.android.authenticator.AuthenticationRunnable;
32 import com.owncloud.android.authenticator.OnAuthenticationResultListener;
33 import com.owncloud.android.authenticator.OnConnectCheckListener;
34 import com.owncloud.android.authenticator.oauth2.OAuth2Context;
35 import com.owncloud.android.authenticator.oauth2.OAuth2GetCodeRunnable;
36 import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener;
37 import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2;
38 import com.owncloud.android.authenticator.oauth2.services.OAuth2GetTokenService;
39 import com.owncloud.android.ui.dialog.SslValidatorDialog;
40 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
41 import com.owncloud.android.utils.OwnCloudVersion;
42 import com.owncloud.android.network.OwnCloudClientUtils;
43 import com.owncloud.android.operations.ConnectionCheckOperation;
44 import com.owncloud.android.operations.ExistenceCheckOperation;
45 import com.owncloud.android.operations.GetOAuth2AccessToken;
46 import com.owncloud.android.operations.OnRemoteOperationListener;
47 import com.owncloud.android.operations.RemoteOperation;
48 import com.owncloud.android.operations.RemoteOperationResult;
49
50 import android.accounts.Account;
51 import android.accounts.AccountAuthenticatorActivity;
52 import android.accounts.AccountManager;
53 import android.app.AlertDialog;
54 import android.app.Dialog;
55 import android.app.ProgressDialog;
56 import android.content.BroadcastReceiver;
57 import android.content.ContentResolver;
58 import android.content.Context;
59 import android.content.DialogInterface;
60 import android.content.Intent;
61 import android.content.IntentFilter;
62 import android.content.SharedPreferences;
63 import android.net.Uri;
64 import android.os.Bundle;
65 import android.os.Handler;
66 import android.preference.PreferenceManager;
67 import android.text.InputType;
68 import android.util.Log;
69 import android.view.View;
70 import android.view.View.OnClickListener;
71 import android.view.View.OnFocusChangeListener;
72 import android.view.Window;
73 import android.widget.CheckBox;
74 import android.widget.EditText;
75 import android.widget.Button;
76 import android.widget.ImageView;
77 import android.widget.TextView;
78 import com.owncloud.android.R;
79
80 import eu.alefzero.webdav.WebdavClient;
81
82 /**
83 * This Activity is used to add an ownCloud account to the App
84 *
85 * @author Bartek Przybylski
86 *
87 */
88 public class AuthenticatorActivity extends AccountAuthenticatorActivity
89 implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener,
90 OnFocusChangeListener, OnClickListener, OnOAuth2GetCodeResultListener {
91
92 private static final int DIALOG_LOGIN_PROGRESS = 0;
93 private static final int DIALOG_SSL_VALIDATOR = 1;
94 private static final int DIALOG_CERT_NOT_SAVED = 2;
95
96 private static final String TAG = "AuthActivity";
97
98 private Thread mAuthThread;
99 private AuthenticationRunnable mAuthRunnable;
100 private ConnectionCheckOperation mConnChkRunnable;
101 private ExistenceCheckOperation mAuthChkOperation;
102 private final Handler mHandler = new Handler();
103 private String mBaseUrl;
104 private OwnCloudVersion mDiscoveredVersion;
105
106 private static final String STATUS_TEXT = "STATUS_TEXT";
107 private static final String STATUS_ICON = "STATUS_ICON";
108 private static final String STATUS_CORRECT = "STATUS_CORRECT";
109 private static final String IS_SSL_CONN = "IS_SSL_CONN";
110 private static final String OC_VERSION = "OC_VERSION";
111 private int mStatusText, mStatusIcon;
112 private boolean mStatusCorrect, mIsSslConn;
113 private RemoteOperationResult mLastSslUntrustedServerResult;
114
115 public static final String PARAM_ACCOUNTNAME = "param_Accountname";
116
117 public static final String PARAM_USERNAME = "param_Username";
118 public static final String PARAM_HOSTNAME = "param_Hostname";
119
120 // oAuth2 variables.
121 private static final int OAUTH2_LOGIN_PROGRESS = 3;
122 private static final String OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT";
123 private static final String OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON";
124 private static final String OAUTH2_CODE_RESULT = "CODE_RESULT";
125 private static final String OAUTH2_IS_CHECKED = "OAUTH2_IS_CHECKED";
126 private Thread mOAuth2GetCodeThread;
127 private OAuth2GetCodeRunnable mOAuth2GetCodeRunnable;
128 private TokenReceiver tokenReceiver;
129 private JSONObject codeResponseJson;
130 private int mOAuth2StatusText, mOAuth2StatusIcon;
131
132 public ConnectorOAuth2 connectorOAuth2;
133
134 // Variables used to save the on the state the contents of all fields.
135 private static final String HOST_URL_TEXT = "HOST_URL_TEXT";
136 private static final String ACCOUNT_USERNAME = "ACCOUNT_USERNAME";
137 private static final String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD";
138
139 //private boolean mNewRedirectUriCaptured;
140 private Uri mNewCapturedUriFromOAuth2Redirection;
141
142 // END of oAuth2 variables.
143
144 @Override
145 protected void onCreate(Bundle savedInstanceState) {
146 super.onCreate(savedInstanceState);
147 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
148 setContentView(R.layout.account_setup);
149 ImageView iv = (ImageView) findViewById(R.id.refreshButton);
150 ImageView iv2 = (ImageView) findViewById(R.id.viewPassword);
151 TextView tv = (TextView) findViewById(R.id.host_URL);
152 TextView tv2 = (TextView) findViewById(R.id.account_password);
153 EditText oauth2Url = (EditText)findViewById(R.id.oAuth_URL);
154 oauth2Url.setText("OWNCLOUD AUTHORIZATION PROVIDER IN TEST");
155
156 if (savedInstanceState != null) {
157 mStatusIcon = savedInstanceState.getInt(STATUS_ICON);
158 mStatusText = savedInstanceState.getInt(STATUS_TEXT);
159 mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT);
160 mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN);
161 setResultIconAndText(mStatusIcon, mStatusText);
162 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
163 if (!mStatusCorrect)
164 iv.setVisibility(View.VISIBLE);
165 else
166 iv.setVisibility(View.INVISIBLE);
167
168 String ocVersion = savedInstanceState.getString(OC_VERSION, null);
169 if (ocVersion != null)
170 mDiscoveredVersion = new OwnCloudVersion(ocVersion);
171
172 // Getting the state of oAuth2 components.
173 mOAuth2StatusIcon = savedInstanceState.getInt(OAUTH2_STATUS_ICON);
174 mOAuth2StatusText = savedInstanceState.getInt(OAUTH2_STATUS_TEXT);
175 // We set this to true if the rotation happens when the user is validating oAuth2 user_code.
176 changeViewByOAuth2Check(savedInstanceState.getBoolean(OAUTH2_IS_CHECKED));
177 // We store a JSon object with all the data returned from oAuth2 server when we get user_code.
178 // Is better than store variable by variable. We use String object to serialize from/to it.
179 try {
180 if (savedInstanceState.containsKey(OAUTH2_CODE_RESULT)) {
181 codeResponseJson = new JSONObject(savedInstanceState.getString(OAUTH2_CODE_RESULT));
182 }
183 } catch (JSONException e) {
184 Log.e(TAG, "onCreate->JSONException: " + e.toString());
185 }
186 // END of getting the state of oAuth2 components.
187
188 // Getting contents of each field.
189 EditText hostUrl = (EditText)findViewById(R.id.host_URL);
190 hostUrl.setText(savedInstanceState.getString(HOST_URL_TEXT), TextView.BufferType.EDITABLE);
191 EditText accountUsername = (EditText)findViewById(R.id.account_username);
192 accountUsername.setText(savedInstanceState.getString(ACCOUNT_USERNAME), TextView.BufferType.EDITABLE);
193 EditText accountPassword = (EditText)findViewById(R.id.account_password);
194 accountPassword.setText(savedInstanceState.getString(ACCOUNT_PASSWORD), TextView.BufferType.EDITABLE);
195 // END of getting contents of each field
196
197 } else {
198 mStatusText = mStatusIcon = 0;
199 mStatusCorrect = false;
200 mIsSslConn = false;
201
202 String accountName = getIntent().getExtras().getString(PARAM_ACCOUNTNAME);
203 String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
204 if (AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType)) {
205 CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
206 oAuth2Check.setChecked(true);
207 changeViewByOAuth2Check(true);
208 }
209
210 if (accountName != null) {
211 ((TextView) findViewById(R.id.account_username)).setText(accountName.substring(0, accountName.lastIndexOf('@')));
212 tv.setText(accountName.substring(accountName.lastIndexOf('@') + 1));
213 }
214 }
215 iv.setOnClickListener(this);
216 iv2.setOnClickListener(this);
217 tv.setOnFocusChangeListener(this);
218 tv2.setOnFocusChangeListener(this);
219
220 Button b = (Button) findViewById(R.id.account_register);
221 if (b != null) {
222 b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));
223 }
224
225 mNewCapturedUriFromOAuth2Redirection = null;
226 }
227
228
229 @Override
230 protected void onNewIntent (Intent intent) {
231 Uri data = intent.getData();
232 //mNewRedirectUriCaptured = (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI));
233 if (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI)) {
234 mNewCapturedUriFromOAuth2Redirection = data;
235 }
236 Log.d(TAG, "onNewIntent()");
237
238 }
239
240
241 @Override
242 protected void onSaveInstanceState(Bundle outState) {
243 outState.putInt(STATUS_ICON, mStatusIcon);
244 outState.putInt(STATUS_TEXT, mStatusText);
245 outState.putBoolean(STATUS_CORRECT, mStatusCorrect);
246 if (mDiscoveredVersion != null)
247 outState.putString(OC_VERSION, mDiscoveredVersion.toString());
248
249 // Saving the state of oAuth2 components.
250 outState.putInt(OAUTH2_STATUS_ICON, mOAuth2StatusIcon);
251 outState.putInt(OAUTH2_STATUS_TEXT, mOAuth2StatusText);
252 CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
253 outState.putBoolean(OAUTH2_IS_CHECKED, oAuth2Check.isChecked());
254 if (codeResponseJson != null){
255 outState.putString(OAUTH2_CODE_RESULT, codeResponseJson.toString());
256 }
257 // END of saving the state of oAuth2 components.
258
259 // Saving contents of each field.
260 outState.putString(HOST_URL_TEXT,((TextView) findViewById(R.id.host_URL)).getText().toString().trim());
261 outState.putString(ACCOUNT_USERNAME,((TextView) findViewById(R.id.account_username)).getText().toString().trim());
262 outState.putString(ACCOUNT_PASSWORD,((TextView) findViewById(R.id.account_password)).getText().toString().trim());
263
264 super.onSaveInstanceState(outState);
265 }
266
267 @Override
268 protected Dialog onCreateDialog(int id) {
269 Dialog dialog = null;
270 switch (id) {
271 case DIALOG_LOGIN_PROGRESS: {
272 ProgressDialog working_dialog = new ProgressDialog(this);
273 working_dialog.setMessage(getResources().getString(
274 R.string.auth_trying_to_login));
275 working_dialog.setIndeterminate(true);
276 working_dialog.setCancelable(true);
277 working_dialog
278 .setOnCancelListener(new DialogInterface.OnCancelListener() {
279 @Override
280 public void onCancel(DialogInterface dialog) {
281 Log.i(TAG, "Login canceled");
282 if (mAuthThread != null) {
283 mAuthThread.interrupt();
284 finish();
285 }
286 }
287 });
288 dialog = working_dialog;
289 break;
290 }
291 // oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser.
292 case OAUTH2_LOGIN_PROGRESS: {
293 ProgressDialog working_dialog = new ProgressDialog(this);
294 try {
295 if (codeResponseJson != null && codeResponseJson.has(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL)) {
296 working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message),
297 codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL),
298 codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE)));
299 } else {
300 working_dialog.setMessage(String.format("Getting authorization"));
301 }
302 } catch (JSONException e) {
303 Log.e(TAG, "onCreateDialog->JSONException: " + e.toString());
304 }
305 working_dialog.setIndeterminate(true);
306 working_dialog.setCancelable(true);
307 working_dialog
308 .setOnCancelListener(new DialogInterface.OnCancelListener() {
309 @Override
310 public void onCancel(DialogInterface dialog) {
311 Log.i(TAG, "Login canceled");
312 if (mOAuth2GetCodeThread != null) {
313 mOAuth2GetCodeThread.interrupt();
314 finish();
315 }
316 if (tokenReceiver != null) {
317 unregisterReceiver(tokenReceiver);
318 tokenReceiver = null;
319 finish();
320 }
321 }
322 });
323 dialog = working_dialog;
324 break;
325 }
326 case DIALOG_SSL_VALIDATOR: {
327 dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);
328 break;
329 }
330 case DIALOG_CERT_NOT_SAVED: {
331 AlertDialog.Builder builder = new AlertDialog.Builder(this);
332 builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
333 builder.setCancelable(false);
334 builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
335 @Override
336 public void onClick(DialogInterface dialog, int which) {
337 dialog.dismiss();
338 };
339 });
340 dialog = builder.create();
341 break;
342 }
343 default:
344 Log.e(TAG, "Incorrect dialog called with id = " + id);
345 }
346 return dialog;
347 }
348
349 @Override
350 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
351 switch (id) {
352 case DIALOG_LOGIN_PROGRESS:
353 case DIALOG_CERT_NOT_SAVED:
354 case OAUTH2_LOGIN_PROGRESS:
355 break;
356 case DIALOG_SSL_VALIDATOR: {
357 ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);
358 break;
359 }
360 default:
361 Log.e(TAG, "Incorrect dialog called with id = " + id);
362 }
363 }
364
365 @Override
366 protected void onResume() {
367 Log.d(TAG, "onResume() start");
368 // (old oauth code) Registering token receiver. We must listening to the service that is pooling to the oAuth server for a token.
369 if (tokenReceiver == null) {
370 IntentFilter tokenFilter = new IntentFilter(OAuth2GetTokenService.TOKEN_RECEIVED_MESSAGE);
371 tokenReceiver = new TokenReceiver();
372 this.registerReceiver(tokenReceiver,tokenFilter);
373 }
374 // (new oauth code)
375 /*if (mNewRedirectUriCaptured) {
376 mNewRedirectUriCaptured = false;*/
377 if (mNewCapturedUriFromOAuth2Redirection != null) {
378 getOAuth2AccessTokenFromCapturedRedirection();
379
380 }
381 super.onResume();
382 }
383
384 @Override
385 protected void onPause() {
386 Log.d(TAG, "onPause() start");
387 super.onPause();
388 }
389
390
391 public void onAuthenticationResult(boolean success, String message) {
392 if (success) {
393 TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password);
394
395 URL url;
396 try {
397 url = new URL(message);
398 } catch (MalformedURLException e) {
399 // should never happen
400 Log.e(getClass().getName(), "Malformed URL: " + message);
401 return;
402 }
403
404 String username = username_text.getText().toString().trim();
405 String accountName = username + "@" + url.getHost();
406 if (url.getPort() >= 0) {
407 accountName += ":" + url.getPort();
408 }
409 Account account = new Account(accountName,
410 AccountAuthenticator.ACCOUNT_TYPE);
411 AccountManager accManager = AccountManager.get(this);
412 accManager.addAccountExplicitly(account, password_text.getText()
413 .toString(), null);
414
415 // Add this account as default in the preferences, if there is none
416 // already
417 Account defaultAccount = AccountUtils
418 .getCurrentOwnCloudAccount(this);
419 if (defaultAccount == null) {
420 SharedPreferences.Editor editor = PreferenceManager
421 .getDefaultSharedPreferences(this).edit();
422 editor.putString("select_oc_account", accountName);
423 editor.commit();
424 }
425
426 final Intent intent = new Intent();
427 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
428 AccountAuthenticator.ACCOUNT_TYPE);
429 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
430 intent.putExtra(AccountManager.KEY_AUTHTOKEN,
431 AccountAuthenticator.ACCOUNT_TYPE);
432 intent.putExtra(AccountManager.KEY_USERDATA, username);
433
434 accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL,
435 url.toString());
436 accManager.setUserData(account,
437 AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString());
438
439 accManager.setUserData(account,
440 AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
441
442 setAccountAuthenticatorResult(intent.getExtras());
443 setResult(RESULT_OK, intent);
444 Bundle bundle = new Bundle();
445 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
446 //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI,
447 // bundle);
448 ContentResolver.requestSync(account, "org.owncloud", bundle);
449
450 /*
451 * if
452 * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion
453 * .owncloud_v2) >= 0) { Intent i = new Intent(this,
454 * ExtensionsAvailableActivity.class); startActivity(i); }
455 */
456
457 finish();
458 } else {
459 try {
460 dismissDialog(DIALOG_LOGIN_PROGRESS);
461 } catch (IllegalArgumentException e) {
462 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
463 }
464 TextView tv = (TextView) findViewById(R.id.account_username);
465 tv.setError(message + " "); // the extra spaces are a workaround for an ugly bug:
466 // 1. insert wrong credentials and connect
467 // 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
468 // 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
469 // Seen, at least, in Android 2.x devices
470 }
471 }
472 public void onCancelClick(View view) {
473 setResult(RESULT_CANCELED);
474 finish();
475 }
476
477 public void onOkClick(View view) {
478 String prefix = "";
479 String url = ((TextView) findViewById(R.id.host_URL)).getText()
480 .toString().trim();
481 if (mIsSslConn) {
482 prefix = "https://";
483 } else {
484 prefix = "http://";
485 }
486 if (url.toLowerCase().startsWith("http://")
487 || url.toLowerCase().startsWith("https://")) {
488 prefix = "";
489 }
490 CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
491 if (oAuth2Check != null && oAuth2Check.isChecked()) {
492 startOauthorization();
493
494 } else {
495 continueConnection(prefix);
496 }
497 }
498
499 private void startOauthorization() {
500 // We start a thread to get an authorization code from the oAuth2 server.
501 setOAuth2ResultIconAndText(R.drawable.progress_small, R.string.oauth_login_connection);
502 mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_F_AUTHORIZATION_ENDPOINT_URL, this);
503 //mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_G_DEVICE_GETCODE_URL, this);
504 mOAuth2GetCodeRunnable.setListener(this, mHandler);
505 mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable);
506 mOAuth2GetCodeThread.start();
507 }
508
509 public void onRegisterClick(View view) {
510 Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));
511 setResult(RESULT_CANCELED);
512 startActivity(register);
513 }
514
515 private void continueConnection(String prefix) {
516 String url = ((TextView) findViewById(R.id.host_URL)).getText()
517 .toString().trim();
518 String username = ((TextView) findViewById(R.id.account_username))
519 .getText().toString();
520 String password = ((TextView) findViewById(R.id.account_password))
521 .getText().toString();
522 if (url.endsWith("/"))
523 url = url.substring(0, url.length() - 1);
524
525 URL uri = null;
526 mDiscoveredVersion = mConnChkRunnable.getDiscoveredVersion();
527 String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false);
528
529 if (webdav_path == null) {
530 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
531 return;
532 }
533
534 try {
535 mBaseUrl = prefix + url;
536 String url_str = prefix + url + webdav_path;
537 uri = new URL(url_str);
538 } catch (MalformedURLException e) {
539 // should never happen
540 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
541 return;
542 }
543
544 showDialog(DIALOG_LOGIN_PROGRESS);
545 mAuthRunnable = new AuthenticationRunnable(uri, username, password, this);
546 mAuthRunnable.setOnAuthenticationResultListener(this, mHandler);
547 mAuthThread = new Thread(mAuthRunnable);
548 mAuthThread.start();
549 }
550
551 @Override
552 public void onConnectionCheckResult(ResultType type) {
553 mStatusText = mStatusIcon = 0;
554 mStatusCorrect = false;
555 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
556 .toString().trim().toLowerCase();
557
558 switch (type) {
559 case OK_SSL:
560 mIsSslConn = true;
561 mStatusIcon = android.R.drawable.ic_secure;
562 mStatusText = R.string.auth_secure_connection;
563 mStatusCorrect = true;
564 break;
565 case OK_NO_SSL:
566 mIsSslConn = false;
567 mStatusCorrect = true;
568 if (t_url.startsWith("http://") ) {
569 mStatusText = R.string.auth_connection_established;
570 mStatusIcon = R.drawable.ic_ok;
571 } else {
572 mStatusText = R.string.auth_nossl_plain_ok_title;
573 mStatusIcon = android.R.drawable.ic_partial_secure;
574 }
575 break;
576 case BAD_OC_VERSION:
577 mStatusIcon = R.drawable.common_error;
578 mStatusText = R.string.auth_bad_oc_version_title;
579 break;
580 case WRONG_CONNECTION:
581 mStatusIcon = R.drawable.common_error;
582 mStatusText = R.string.auth_wrong_connection_title;
583 break;
584 case TIMEOUT:
585 mStatusIcon = R.drawable.common_error;
586 mStatusText = R.string.auth_timeout_title;
587 break;
588 case INCORRECT_ADDRESS:
589 mStatusIcon = R.drawable.common_error;
590 mStatusText = R.string.auth_incorrect_address_title;
591 break;
592 case SSL_UNVERIFIED_SERVER:
593 mStatusIcon = R.drawable.common_error;
594 mStatusText = R.string.auth_ssl_unverified_server_title;
595 break;
596 case SSL_INIT_ERROR:
597 mStatusIcon = R.drawable.common_error;
598 mStatusText = R.string.auth_ssl_general_error_title;
599 break;
600 case HOST_NOT_AVAILABLE:
601 mStatusIcon = R.drawable.common_error;
602 mStatusText = R.string.auth_unknown_host_title;
603 break;
604 case NO_NETWORK_CONNECTION:
605 mStatusIcon = R.drawable.no_network;
606 mStatusText = R.string.auth_no_net_conn_title;
607 break;
608 case INSTANCE_NOT_CONFIGURED:
609 mStatusIcon = R.drawable.common_error;
610 mStatusText = R.string.auth_not_configured_title;
611 break;
612 case UNKNOWN_ERROR:
613 mStatusIcon = R.drawable.common_error;
614 mStatusText = R.string.auth_unknown_error_title;
615 break;
616 case FILE_NOT_FOUND:
617 mStatusIcon = R.drawable.common_error;
618 mStatusText = R.string.auth_incorrect_path_title;
619 break;
620 default:
621 Log.e(TAG, "Incorrect connection checker result type: " + type);
622 }
623 setResultIconAndText(mStatusIcon, mStatusText);
624 if (!mStatusCorrect)
625 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
626 else
627 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
628 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
629 }
630
631 public void onFocusChange(View view, boolean hasFocus) {
632 if (view.getId() == R.id.host_URL) {
633 if (!hasFocus) {
634 TextView tv = ((TextView) findViewById(R.id.host_URL));
635 String uri = tv.getText().toString().trim();
636 if (uri.length() != 0) {
637 setResultIconAndText(R.drawable.progress_small,
638 R.string.auth_testing_connection);
639 //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
640 mConnChkRunnable = new ConnectionCheckOperation(uri, this);
641 //mConnChkRunnable.setListener(this, mHandler);
642 //mAuthThread = new Thread(mConnChkRunnable);
643 //mAuthThread.start();
644 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this);
645 mDiscoveredVersion = null;
646 mAuthThread = mConnChkRunnable.execute(client, this, mHandler);
647 } else {
648 findViewById(R.id.refreshButton).setVisibility(
649 View.INVISIBLE);
650 setResultIconAndText(0, 0);
651 }
652 } else {
653 // avoids that the 'connect' button can be clicked if the test was previously passed
654 findViewById(R.id.buttonOK).setEnabled(false);
655 }
656 } else if (view.getId() == R.id.account_password) {
657 ImageView iv = (ImageView) findViewById(R.id.viewPassword);
658 if (hasFocus) {
659 iv.setVisibility(View.VISIBLE);
660 } else {
661 TextView v = (TextView) findViewById(R.id.account_password);
662 int input_type = InputType.TYPE_CLASS_TEXT
663 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
664 v.setInputType(input_type);
665 iv.setVisibility(View.INVISIBLE);
666 }
667 }
668 }
669
670 private void setResultIconAndText(int drawable_id, int text_id) {
671 ImageView iv = (ImageView) findViewById(R.id.action_indicator);
672 TextView tv = (TextView) findViewById(R.id.status_text);
673
674 if (drawable_id == 0 && text_id == 0) {
675 iv.setVisibility(View.INVISIBLE);
676 tv.setVisibility(View.INVISIBLE);
677 } else {
678 iv.setImageResource(drawable_id);
679 tv.setText(text_id);
680 iv.setVisibility(View.VISIBLE);
681 tv.setVisibility(View.VISIBLE);
682 }
683 }
684
685 @Override
686 public void onClick(View v) {
687 if (v.getId() == R.id.refreshButton) {
688 onFocusChange(findViewById(R.id.host_URL), false);
689 } else if (v.getId() == R.id.viewPassword) {
690 EditText view = (EditText) findViewById(R.id.account_password);
691 int selectionStart = view.getSelectionStart();
692 int selectionEnd = view.getSelectionEnd();
693 int input_type = view.getInputType();
694 if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
695 input_type = InputType.TYPE_CLASS_TEXT
696 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
697 } else {
698 input_type = InputType.TYPE_CLASS_TEXT
699 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
700 }
701 view.setInputType(input_type);
702 view.setSelection(selectionStart, selectionEnd);
703 }
704 }
705
706 @Override protected void onDestroy() {
707 // We must stop the service thats it's pooling to oAuth2 server for a token.
708 Intent tokenService = new Intent(this, OAuth2GetTokenService.class);
709 stopService(tokenService);
710
711 // We stop listening the result of the pooling service.
712 if (tokenReceiver != null) {
713 unregisterReceiver(tokenReceiver);
714 tokenReceiver = null;
715 finish();
716 }
717
718 super.onDestroy();
719 }
720
721 // Controlling the oAuth2 checkbox on the activity: hide and show widgets.
722 public void onOff_check_Click(View view) {
723 CheckBox oAuth2Check = (CheckBox)view;
724 changeViewByOAuth2Check(oAuth2Check.isChecked());
725
726 }
727
728 public void changeViewByOAuth2Check(Boolean checked) {
729
730 EditText oAuth2Url = (EditText) findViewById(R.id.oAuth_URL);
731 EditText accountUsername = (EditText) findViewById(R.id.account_username);
732 EditText accountPassword = (EditText) findViewById(R.id.account_password);
733 ImageView viewPassword = (ImageView) findViewById(R.id.viewPassword);
734 ImageView auth2ActionIndicator = (ImageView) findViewById(R.id.auth2_action_indicator);
735 TextView oauth2StatusText = (TextView) findViewById(R.id.oauth2_status_text);
736
737 if (checked) {
738 oAuth2Url.setVisibility(View.VISIBLE);
739 accountUsername.setVisibility(View.GONE);
740 accountPassword.setVisibility(View.GONE);
741 viewPassword.setVisibility(View.GONE);
742 auth2ActionIndicator.setVisibility(View.INVISIBLE);
743 oauth2StatusText.setVisibility(View.INVISIBLE);
744 } else {
745 oAuth2Url.setVisibility(View.GONE);
746 accountUsername.setVisibility(View.VISIBLE);
747 accountPassword.setVisibility(View.VISIBLE);
748 viewPassword.setVisibility(View.INVISIBLE);
749 auth2ActionIndicator.setVisibility(View.GONE);
750 oauth2StatusText.setVisibility(View.GONE);
751 }
752
753 }
754
755 // Controlling the oAuth2 result of server connection.
756 private void setOAuth2ResultIconAndText(int drawable_id, int text_id) {
757 ImageView iv = (ImageView) findViewById(R.id.auth2_action_indicator);
758 TextView tv = (TextView) findViewById(R.id.oauth2_status_text);
759
760 if (drawable_id == 0 && text_id == 0) {
761 iv.setVisibility(View.INVISIBLE);
762 tv.setVisibility(View.INVISIBLE);
763 } else {
764 iv.setImageResource(drawable_id);
765 tv.setText(text_id);
766 iv.setVisibility(View.VISIBLE);
767 tv.setVisibility(View.VISIBLE);
768 }
769 }
770
771 // Results from the first call to oAuth2 server : getting the user_code and verification_url.
772 @Override
773 public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject responseJson) {
774 if ((type == ResultOAuthType.OK_SSL)||(type == ResultOAuthType.OK_NO_SSL)) {
775 codeResponseJson = responseJson;
776 if (codeResponseJson != null) {
777 getOAuth2AccessTokenFromJsonResponse();
778 } // else - nothing to do here - wait for callback !!!
779
780 } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) {
781 setOAuth2ResultIconAndText(R.drawable.common_error, R.string.oauth_connection_url_unavailable);
782 }
783 }
784
785 // If the results of getting the user_code and verification_url are OK, we get the received data and we start
786 // the polling service to oAuth2 server to get a valid token.
787 private void getOAuth2AccessTokenFromJsonResponse() {
788 String deviceCode = null;
789 String verificationUrl = null;
790 String userCode = null;
791 int expiresIn = -1;
792 int interval = -1;
793
794 Log.d(TAG, "ResponseOAuth2->" + codeResponseJson.toString());
795
796 try {
797 // We get data that we must show to the user or we will use internally.
798 verificationUrl = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL);
799 userCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE);
800 expiresIn = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_EXPIRES_IN);
801
802 // And we get data that we must use to get a token.
803 deviceCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_DEVICE_CODE);
804 interval = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_INTERVAL);
805
806 } catch (JSONException e) {
807 Log.e(TAG, "Exception accesing data in Json object" + e.toString());
808 }
809
810 // Updating status widget to OK.
811 setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established);
812
813 // Showing the dialog with instructions for the user.
814 showDialog(OAUTH2_LOGIN_PROGRESS);
815
816 // Loggin all the data.
817 Log.d(TAG, "verificationUrl->" + verificationUrl);
818 Log.d(TAG, "userCode->" + userCode);
819 Log.d(TAG, "deviceCode->" + deviceCode);
820 Log.d(TAG, "expiresIn->" + expiresIn);
821 Log.d(TAG, "interval->" + interval);
822
823 // Starting the pooling service.
824 try {
825 Intent tokenService = new Intent(this, OAuth2GetTokenService.class);
826 tokenService.putExtra(OAuth2GetTokenService.TOKEN_URI, OAuth2Context.OAUTH2_G_DEVICE_GETTOKEN_URL);
827 tokenService.putExtra(OAuth2GetTokenService.TOKEN_DEVICE_CODE, deviceCode);
828 tokenService.putExtra(OAuth2GetTokenService.TOKEN_INTERVAL, interval);
829
830 startService(tokenService);
831 }
832 catch (Exception e) {
833 Log.e(TAG, "tokenService creation problem :", e);
834 }
835
836 }
837
838 private void getOAuth2AccessTokenFromCapturedRedirection() {
839 Map<String, String> responseValues = new HashMap<String, String>();
840 //String queryParameters = getIntent().getData().getQuery();
841 String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
842 mNewCapturedUriFromOAuth2Redirection = null;
843
844 Log.v(TAG, "Queryparameters (Code) = " + queryParameters);
845
846 String[] pairs = queryParameters.split("&");
847 Log.v(TAG, "Pairs (Code) = " + pairs.toString());
848
849 int i = 0;
850 String key = "";
851 String value = "";
852
853 StringBuilder sb = new StringBuilder();
854
855 while (pairs.length > i) {
856 int j = 0;
857 String[] part = pairs[i].split("=");
858
859 while (part.length > j) {
860 String p = part[j];
861 if (j == 0) {
862 key = p;
863 sb.append(key + " = ");
864 } else if (j == 1) {
865 value = p;
866 responseValues.put(key, value);
867 sb.append(value + "\n");
868 }
869
870 Log.v(TAG, "[" + i + "," + j + "] = " + p);
871 j++;
872 }
873 i++;
874 }
875
876
877 // Updating status widget to OK.
878 setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established);
879
880 // Showing the dialog with instructions for the user.
881 showDialog(OAUTH2_LOGIN_PROGRESS);
882
883 //
884 RemoteOperation operation = new GetOAuth2AccessToken(responseValues);
885 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(OAuth2Context.OAUTH2_F_TOKEN_ENDPOINT_URL), getApplicationContext());
886 operation.execute(client, this, mHandler);
887 }
888
889
890
891 // We get data from the oAuth2 token service with this broadcast receiver.
892 private class TokenReceiver extends BroadcastReceiver {
893 /**
894 * The token is received.
895 * @author
896 * {@link BroadcastReceiver} to enable oAuth2 token receiving.
897 */
898 @Override
899 public void onReceive(Context context, Intent intent) {
900 @SuppressWarnings("unchecked")
901 HashMap<String, String> tokenResponse = (HashMap<String, String>)intent.getExtras().get(OAuth2GetTokenService.TOKEN_RECEIVED_DATA);
902 Log.d(TAG, "TokenReceiver->" + tokenResponse.get(OAuth2GetTokenService.TOKEN_ACCESS_TOKEN));
903 dismissDialog(OAUTH2_LOGIN_PROGRESS);
904
905 }
906 }
907
908 @Override
909 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
910 if (operation instanceof ConnectionCheckOperation) {
911
912 mStatusText = mStatusIcon = 0;
913 mStatusCorrect = false;
914 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
915 .toString().trim().toLowerCase();
916
917 switch (result.getCode()) {
918 case OK_SSL:
919 mIsSslConn = true;
920 mStatusIcon = android.R.drawable.ic_secure;
921 mStatusText = R.string.auth_secure_connection;
922 mStatusCorrect = true;
923 break;
924
925 case OK_NO_SSL:
926 case OK:
927 mIsSslConn = false;
928 mStatusCorrect = true;
929 if (t_url.startsWith("http://") ) {
930 mStatusText = R.string.auth_connection_established;
931 mStatusIcon = R.drawable.ic_ok;
932 } else {
933 mStatusText = R.string.auth_nossl_plain_ok_title;
934 mStatusIcon = android.R.drawable.ic_partial_secure;
935 }
936 break;
937
938
939 case BAD_OC_VERSION:
940 mStatusIcon = R.drawable.common_error;
941 mStatusText = R.string.auth_bad_oc_version_title;
942 break;
943 case WRONG_CONNECTION:
944 mStatusIcon = R.drawable.common_error;
945 mStatusText = R.string.auth_wrong_connection_title;
946 break;
947 case TIMEOUT:
948 mStatusIcon = R.drawable.common_error;
949 mStatusText = R.string.auth_timeout_title;
950 break;
951 case INCORRECT_ADDRESS:
952 mStatusIcon = R.drawable.common_error;
953 mStatusText = R.string.auth_incorrect_address_title;
954 break;
955
956 case SSL_RECOVERABLE_PEER_UNVERIFIED:
957 mStatusIcon = R.drawable.common_error;
958 mStatusText = R.string.auth_ssl_unverified_server_title;
959 mLastSslUntrustedServerResult = result;
960 showDialog(DIALOG_SSL_VALIDATOR);
961 break;
962
963 case SSL_ERROR:
964 mStatusIcon = R.drawable.common_error;
965 mStatusText = R.string.auth_ssl_general_error_title;
966 break;
967
968 case HOST_NOT_AVAILABLE:
969 mStatusIcon = R.drawable.common_error;
970 mStatusText = R.string.auth_unknown_host_title;
971 break;
972 case NO_NETWORK_CONNECTION:
973 mStatusIcon = R.drawable.no_network;
974 mStatusText = R.string.auth_no_net_conn_title;
975 break;
976 case INSTANCE_NOT_CONFIGURED:
977 mStatusIcon = R.drawable.common_error;
978 mStatusText = R.string.auth_not_configured_title;
979 break;
980 case FILE_NOT_FOUND:
981 mStatusIcon = R.drawable.common_error;
982 mStatusText = R.string.auth_incorrect_path_title;
983 break;
984 case UNHANDLED_HTTP_CODE:
985 case UNKNOWN_ERROR:
986 mStatusIcon = R.drawable.common_error;
987 mStatusText = R.string.auth_unknown_error_title;
988 break;
989 default:
990 Log.e(TAG, "Incorrect connection checker result type: " + result.getHttpCode());
991 }
992 setResultIconAndText(mStatusIcon, mStatusText);
993 if (!mStatusCorrect)
994 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
995 else
996 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
997 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
998
999 } else if (operation instanceof GetOAuth2AccessToken) {
1000
1001 try {
1002 dismissDialog(OAUTH2_LOGIN_PROGRESS);
1003 } catch (IllegalArgumentException e) {
1004 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
1005 }
1006
1007 if (result.isSuccess()) {
1008
1009 /// time to test the retrieved access token on the ownCloud server
1010 String url = ((TextView) findViewById(R.id.host_URL)).getText()
1011 .toString().trim();
1012 if (url.endsWith("/"))
1013 url = url.substring(0, url.length() - 1);
1014
1015 Uri uri = null;
1016 /*String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion);
1017
1018 if (webdav_path == null) {
1019 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
1020 return;
1021 }*/
1022
1023 String prefix = "";
1024 if (mIsSslConn) {
1025 prefix = "https://";
1026 } else {
1027 prefix = "http://";
1028 }
1029 if (url.toLowerCase().startsWith("http://")
1030 || url.toLowerCase().startsWith("https://")) {
1031 prefix = "";
1032 }
1033
1034 try {
1035 mBaseUrl = prefix + url;
1036 //String url_str = prefix + url + webdav_path;
1037 String url_str = prefix + url + "/remote.php/odav";
1038 uri = Uri.parse(url_str);
1039
1040 } catch (Exception e) {
1041 // should never happen
1042 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
1043 return;
1044 }
1045
1046 showDialog(DIALOG_LOGIN_PROGRESS);
1047 String accessToken = ((GetOAuth2AccessToken)operation).getResultTokenMap().get(OAuth2Context.KEY_ACCESS_TOKEN);
1048 Log.d(TAG, "Got ACCESS TOKEN: " + accessToken);
1049 mAuthChkOperation = new ExistenceCheckOperation("", this, accessToken);
1050 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(uri, getApplicationContext());
1051 mAuthChkOperation.execute(client, this, mHandler);
1052
1053
1054 } else {
1055 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1056 tv.setError("A valid authorization could not be obtained");
1057
1058 }
1059
1060 } else if (operation instanceof ExistenceCheckOperation) {
1061
1062 try {
1063 dismissDialog(DIALOG_LOGIN_PROGRESS);
1064 } catch (IllegalArgumentException e) {
1065 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
1066 }
1067
1068 if (result.isSuccess()) {
1069 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1070 Log.d(TAG, "Checked access - time to save the account");
1071
1072 Uri uri = Uri.parse(mBaseUrl);
1073 String username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
1074 String accountName = username + "@" + uri.getHost();
1075 if (uri.getPort() >= 0) {
1076 accountName += ":" + uri.getPort();
1077 }
1078 // TODO - check that accountName does not exist
1079 Account account = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE);
1080 AccountManager accManager = AccountManager.get(this);
1081 accManager.addAccountExplicitly(account, "", null); // with our implementation, the password is never input in the app
1082
1083 // Add this account as default in the preferences, if there is none
1084 Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
1085 if (defaultAccount == null) {
1086 SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
1087 editor.putString("select_oc_account", accountName);
1088 editor.commit();
1089 }
1090
1091 /// account data to save by the AccountManager
1092 final Intent intent = new Intent();
1093 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE);
1094 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
1095 intent.putExtra(AccountManager.KEY_USERDATA, username);
1096
1097 accManager.setAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, ((ExistenceCheckOperation) operation).getAccessToken());
1098
1099 accManager.setUserData(account, AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable.getDiscoveredVersion().toString());
1100 accManager.setUserData(account, AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
1101 accManager.setUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE");
1102
1103 setAccountAuthenticatorResult(intent.getExtras());
1104 setResult(RESULT_OK, intent);
1105
1106 /// enforce the first account synchronization
1107 Bundle bundle = new Bundle();
1108 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1109 ContentResolver.requestSync(account, "org.owncloud", bundle);
1110
1111 finish();
1112
1113 } else {
1114 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1115 tv.setError(result.getLogMessage());
1116 Log.d(TAG, "Access failed: " + result.getLogMessage());
1117 }
1118 }
1119 }
1120
1121
1122 public void onSavedCertificate() {
1123 mAuthThread = mConnChkRunnable.retry(this, mHandler);
1124 }
1125
1126 @Override
1127 public void onFailedSavingCertificate() {
1128 showDialog(DIALOG_CERT_NOT_SAVED);
1129 }
1130
1131 }