michael@0: /** michael@0: * Copyright (c) 2012-2013, Gerald Garcia michael@8: * michael@0: * This file is part of Andoid Caldav Sync Adapter Free. michael@0: * michael@0: * Andoid Caldav Sync Adapter Free is free software: you can redistribute michael@0: * it and/or modify it under the terms of the GNU General Public License michael@0: * as published by the Free Software Foundation, either version 3 of the michael@0: * License, or at your option any later version. michael@0: * michael@0: * Andoid Caldav Sync Adapter Free is distributed in the hope that michael@0: * it will be useful, but WITHOUT ANY WARRANTY; without even the implied michael@0: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the michael@0: * GNU General Public License for more details. michael@0: * michael@0: * You should have received a copy of the GNU General Public License michael@0: * along with Andoid Caldav Sync Adapter Free. michael@0: * If not, see . michael@8: * michael@0: */ michael@0: michael@0: package org.gege.caldavsyncadapter.authenticator; michael@0: michael@0: import android.accounts.Account; michael@0: import android.accounts.AccountManager; michael@0: import android.animation.Animator; michael@0: import android.animation.AnimatorListenerAdapter; michael@0: import android.annotation.TargetApi; michael@0: import android.app.Activity; michael@0: import android.content.Context; michael@0: import android.content.pm.PackageManager.NameNotFoundException; michael@0: import android.os.AsyncTask; michael@0: import android.os.Build; michael@0: import android.os.Bundle; michael@8: import android.text.Editable; michael@9: import android.text.InputType; michael@0: import android.text.TextUtils; michael@8: import android.text.TextWatcher; michael@0: import android.util.Log; michael@0: import android.view.KeyEvent; michael@0: import android.view.Menu; michael@0: import android.view.View; michael@0: import android.view.inputmethod.EditorInfo; michael@8: import android.widget.CheckBox; michael@9: import android.widget.CompoundButton; michael@0: import android.widget.EditText; michael@0: import android.widget.TextView; michael@0: import android.widget.Toast; michael@0: michael@8: import org.apache.http.conn.HttpHostConnectException; michael@8: import org.gege.caldavsyncadapter.Constants; michael@8: import org.gege.caldavsyncadapter.R; michael@8: import org.gege.caldavsyncadapter.caldav.CaldavFacade; michael@8: import org.gege.caldavsyncadapter.caldav.CaldavFacade.TestConnectionResult; michael@8: import org.xml.sax.SAXException; michael@8: michael@8: import java.io.IOException; michael@8: import java.io.UnsupportedEncodingException; michael@8: import java.net.MalformedURLException; michael@8: import java.net.URISyntaxException; michael@8: import java.util.Locale; michael@8: michael@8: import javax.xml.parsers.ParserConfigurationException; michael@8: michael@0: /** michael@0: * Activity which displays a login screen to the user, offering registration as michael@0: * well. michael@0: */ michael@0: public class AuthenticatorActivity extends Activity { michael@0: michael@8: private static final String TAG = "AuthenticatorActivity"; michael@0: michael@8: private static final String ACCOUNT_TYPE = "org.gege.caldavsyncadapter.account"; michael@0: michael@8: public static final String USER_DATA_URL_KEY = "USER_DATA_URL_KEY"; michael@8: public static final String USER_DATA_USERNAME = "USER_DATA_USERNAME"; michael@8: public static final String USER_DATA_VERSION = "USER_DATA_VERSION"; michael@8: public static final String CURRENT_USER_DATA_VERSION = "1"; michael@0: michael@8: public static final String ACCOUNT_NAME_SPLITTER = "@"; michael@0: michael@8: /** michael@8: * The default email to populate the email field with. michael@8: */ michael@8: public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL"; michael@0: michael@8: /** michael@8: * Keep track of the login task to ensure we can cancel it if requested. michael@8: */ michael@8: private UserLoginTask mAuthTask = null; michael@0: michael@8: // Values for email and password at the time of the login attempt. michael@8: private String mUser; michael@8: private String mPassword; michael@8: private String mTrustAll; michael@8: private Context mContext; michael@0: michael@8: // UI references. michael@8: private EditText mUserView; michael@8: private EditText mPasswordView; michael@8: private View mLoginFormView; michael@8: private View mLoginStatusView; michael@8: private TextView mLoginStatusMessageView; michael@8: private CheckBox mTrustCheckBox; michael@0: michael@8: private AccountManager mAccountManager; michael@0: michael@8: private String mURL; michael@8: private EditText mURLView; michael@0: michael@8: private String mAccountname; michael@8: private EditText mAccountnameView; michael@0: michael@9: private CheckBox showPassword; michael@9: michael@8: public AuthenticatorActivity() { michael@8: super(); michael@0: michael@8: } michael@0: michael@8: @Override michael@8: protected void onCreate(Bundle savedInstanceState) { michael@8: super.onCreate(savedInstanceState); michael@0: michael@8: mAccountManager = AccountManager.get(this); michael@0: michael@8: setContentView(R.layout.activity_authenticator); michael@0: michael@8: // Set up the login form. michael@8: mUser = getIntent().getStringExtra(EXTRA_EMAIL); michael@8: mUserView = (EditText) findViewById(R.id.user); michael@8: mUserView.setText(mUser); michael@9: mUserView.requestFocus(); michael@0: michael@8: mContext = getBaseContext(); michael@0: michael@8: mPasswordView = (EditText) findViewById(R.id.password); michael@8: mPasswordView michael@8: .setOnEditorActionListener(new TextView.OnEditorActionListener() { michael@8: @Override michael@8: public boolean onEditorAction(TextView textView, int id, michael@8: KeyEvent keyEvent) { michael@8: if (id == R.id.login || id == EditorInfo.IME_NULL) { michael@8: attemptLogin(); michael@8: return true; michael@8: } michael@8: return false; michael@8: } michael@8: }); michael@0: michael@9: showPassword = (CheckBox) findViewById(R.id.showPassword); michael@9: showPassword.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { michael@9: @Override michael@9: public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { michael@9: if(isChecked) { michael@9: mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); michael@9: } else { michael@9: mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); michael@9: } michael@9: } michael@9: }); michael@0: michael@8: mURLView = (EditText) findViewById(R.id.url); michael@8: // if the URL start with "https" show the option to disable SSL host verification michael@8: mURLView.addTextChangedListener(new TextWatcher() { michael@8: @Override michael@8: public void onTextChanged(CharSequence s, int start, int before, int count) { michael@8: String url = ((EditText) findViewById(R.id.url)).getText().toString(); michael@8: int visible = url.toLowerCase(Locale.getDefault()) michael@8: .startsWith("https") ? View.VISIBLE : View.GONE; michael@8: ((CheckBox) findViewById(R.id.trustall)).setVisibility(visible); michael@8: } michael@0: michael@8: @Override michael@8: public void beforeTextChanged(CharSequence s, int start, int count, michael@8: int after) { michael@8: } michael@0: michael@8: @Override michael@8: public void afterTextChanged(Editable s) { michael@8: } michael@8: }); michael@0: michael@8: mAccountnameView = (EditText) findViewById(R.id.accountname); michael@0: michael@8: mLoginFormView = findViewById(R.id.login_form); michael@8: mLoginStatusView = findViewById(R.id.login_status); michael@8: mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message); michael@0: michael@8: findViewById(R.id.sign_in_button).setOnClickListener( michael@8: new View.OnClickListener() { michael@8: @Override michael@8: public void onClick(View view) { michael@8: attemptLogin(); michael@8: } michael@8: } michael@8: ); michael@0: michael@8: mTrustCheckBox = (CheckBox) findViewById(R.id.trustall); michael@0: michael@0: michael@8: } michael@0: michael@8: @Override michael@8: public boolean onCreateOptionsMenu(Menu menu) { michael@8: super.onCreateOptionsMenu(menu); michael@8: return true; michael@8: } michael@0: michael@8: /** michael@8: * Attempts to sign in or register the account specified by the login form. michael@8: * If there are form errors (invalid email, missing fields, etc.), the michael@8: * errors are presented and no actual login attempt is made. michael@8: */ michael@8: public void attemptLogin() { michael@8: if (mAuthTask != null) { michael@8: return; michael@8: } michael@0: michael@8: // Reset errors. michael@8: mUserView.setError(null); michael@8: mPasswordView.setError(null); michael@8: michael@8: // Store values at the time of the login attempt. michael@8: mUser = mUserView.getText().toString(); michael@8: mPassword = mPasswordView.getText().toString(); michael@8: mURL = mURLView.getText().toString(); michael@8: mAccountname = mAccountnameView.getText().toString(); michael@8: mTrustAll = (mTrustCheckBox.isChecked() ? "false" : "true"); michael@8: boolean cancel = false; michael@8: View focusView = null; michael@8: michael@8: if (!mAccountname.equals("")) { michael@8: Account TestAccount = new Account(mAccountname, ACCOUNT_TYPE); michael@8: String TestUrl = mAccountManager.getUserData(TestAccount, AuthenticatorActivity.USER_DATA_URL_KEY); michael@8: if (TestUrl != null) { michael@8: mAccountnameView.setError(getString(R.string.error_account_already_in_use)); michael@8: focusView = mAccountnameView; michael@8: cancel = true; michael@8: } michael@8: } michael@8: michael@8: // Check for a valid password. michael@8: if (TextUtils.isEmpty(mPassword)) { michael@8: mPasswordView.setError(getString(R.string.error_field_required)); michael@8: focusView = mPasswordView; michael@8: cancel = true; michael@8: } michael@8: michael@8: // Check for a valid email address. michael@8: if (TextUtils.isEmpty(mUser)) { michael@8: mUserView.setError(getString(R.string.error_field_required)); michael@8: focusView = mUserView; michael@8: cancel = true; michael@8: } michael@8: //else if (!mUser.contains("@")) { michael@8: // mUserView.setError(getString(R.string.error_invalid_email)); michael@8: // focusView = mUserView; michael@8: // cancel = true; michael@8: //} michael@8: michael@8: if (cancel) { michael@8: // There was an error; don't attempt login and focus the first michael@8: // form field with an error. michael@8: focusView.requestFocus(); michael@8: } else { michael@8: // Show a progress spinner, and kick off a background task to michael@8: // perform the user login attempt. michael@8: mLoginStatusMessageView.setText(R.string.login_progress_signing_in); michael@8: showProgress(true); michael@8: mAuthTask = new UserLoginTask(); michael@8: mAuthTask.execute((Void) null); michael@8: } michael@8: } michael@8: michael@8: /** michael@8: * Shows the progress UI and hides the login form. michael@8: */ michael@8: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) michael@8: private void showProgress(final boolean show) { michael@8: // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow michael@8: // for very easy animations. If available, use these APIs to fade-in michael@8: // the progress spinner. michael@8: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { michael@8: int shortAnimTime = getResources().getInteger( michael@8: android.R.integer.config_shortAnimTime); michael@8: michael@8: mLoginStatusView.setVisibility(View.VISIBLE); michael@8: mLoginStatusView.animate().setDuration(shortAnimTime) michael@8: .alpha(show ? 1 : 0) michael@8: .setListener(new AnimatorListenerAdapter() { michael@8: @Override michael@8: public void onAnimationEnd(Animator animation) { michael@8: mLoginStatusView.setVisibility(show ? View.VISIBLE michael@8: : View.GONE); michael@8: } michael@8: }); michael@8: michael@8: mLoginFormView.setVisibility(View.VISIBLE); michael@8: mLoginFormView.animate().setDuration(shortAnimTime) michael@8: .alpha(show ? 0 : 1) michael@8: .setListener(new AnimatorListenerAdapter() { michael@8: @Override michael@8: public void onAnimationEnd(Animator animation) { michael@8: mLoginFormView.setVisibility(show ? View.GONE michael@8: : View.VISIBLE); michael@8: } michael@8: }); michael@8: } else { michael@8: // The ViewPropertyAnimator APIs are not available, so simply show michael@8: // and hide the relevant UI components. michael@8: mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); michael@8: mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); michael@8: } michael@8: } michael@8: michael@8: michael@8: protected enum LoginResult { michael@8: MalformedURLException, michael@8: GeneralSecurityException, michael@8: UnkonwnException, michael@8: WrongCredentials, michael@8: InvalidResponse, michael@8: WrongUrl, michael@8: ConnectionRefused, michael@8: Success_Calendar, michael@8: Success_Collection, michael@8: UNTRUSTED_CERT, michael@8: Account_Already_In_Use michael@8: } michael@8: michael@8: michael@8: /** michael@8: * Represents an asynchronous login/registration task used to authenticate michael@8: * the user. michael@8: */ michael@8: public class UserLoginTask extends AsyncTask { michael@8: michael@8: @Override michael@8: protected LoginResult doInBackground(Void... params) { michael@8: michael@8: TestConnectionResult result = null; michael@8: michael@8: try { michael@8: CaldavFacade facade = new CaldavFacade(mUser, mPassword, mURL, mTrustAll); michael@8: String version = ""; michael@8: try { michael@8: version = mContext.getPackageManager() michael@8: .getPackageInfo(mContext.getPackageName(), 0).versionName; michael@8: } catch (NameNotFoundException e) { michael@8: version = "unknown"; michael@8: e.printStackTrace(); michael@8: } michael@8: facade.setVersion(version); michael@8: result = facade.testConnection(); michael@8: Log.i(TAG, "testConnection status=" + result); michael@8: } catch (HttpHostConnectException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.ConnectionRefused; michael@8: } catch (MalformedURLException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.MalformedURLException; michael@8: } catch (UnsupportedEncodingException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.UnkonwnException; michael@8: } catch (ParserConfigurationException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.UnkonwnException; michael@8: } catch (SAXException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.InvalidResponse; michael@8: } catch (IOException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.UnkonwnException; michael@8: } catch (URISyntaxException e) { michael@8: Log.w(TAG, "testConnection", e); michael@8: return LoginResult.MalformedURLException; michael@8: } michael@8: michael@8: if (result == null) { michael@8: return LoginResult.UnkonwnException; michael@8: } michael@8: michael@8: switch (result) { michael@8: michael@8: case SSL_ERROR: michael@8: return LoginResult.UNTRUSTED_CERT; michael@8: case SUCCESS: michael@8: boolean OldAccount = false; michael@8: LoginResult Result = LoginResult.Success_Calendar; michael@8: michael@8: if (OldAccount) { michael@8: final Account account = new Account(mUser, ACCOUNT_TYPE); michael@8: if (mAccountManager.addAccountExplicitly(account, mPassword, null)) { michael@8: Log.v(TAG, "new account created"); michael@8: mAccountManager.setUserData(account, USER_DATA_URL_KEY, mURL); michael@8: } else { michael@8: Log.v(TAG, "no new account created"); michael@8: Result = LoginResult.Account_Already_In_Use; michael@8: } michael@8: } else { michael@8: final Account account; michael@8: if (mAccountname.equals("")) { michael@8: account = new Account(mUser + ACCOUNT_NAME_SPLITTER + mURL, ACCOUNT_TYPE); michael@8: } else { michael@8: account = new Account(mAccountname, ACCOUNT_TYPE); michael@8: } michael@8: if (mAccountManager.addAccountExplicitly(account, mPassword, null)) { michael@8: Log.v(TAG, "new account created"); michael@8: mAccountManager.setUserData(account, USER_DATA_URL_KEY, mURL); michael@8: mAccountManager.setUserData(account, USER_DATA_USERNAME, mUser); michael@8: mAccountManager.setUserData(account, USER_DATA_VERSION, CURRENT_USER_DATA_VERSION); michael@8: mAccountManager.setUserData(account, Constants.USER_DATA_TRUST_ALL_KEY, mTrustAll); michael@8: } else { michael@8: Log.v(TAG, "no new account created"); michael@8: Result = LoginResult.Account_Already_In_Use; michael@8: } michael@8: } michael@8: michael@8: return Result; michael@8: michael@8: case WRONG_CREDENTIAL: michael@8: return LoginResult.WrongCredentials; michael@8: michael@8: case WRONG_SERVER_STATUS: michael@8: return LoginResult.InvalidResponse; michael@8: michael@8: case WRONG_URL: michael@8: return LoginResult.WrongUrl; michael@8: michael@8: case WRONG_ANSWER: michael@8: return LoginResult.InvalidResponse; michael@8: michael@8: default: michael@8: return LoginResult.UnkonwnException; michael@8: michael@8: } michael@8: michael@8: } michael@8: michael@8: michael@8: @Override michael@8: protected void onPostExecute(final LoginResult result) { michael@8: mAuthTask = null; michael@8: showProgress(false); michael@8: michael@8: int duration = Toast.LENGTH_SHORT; michael@8: Toast toast = null; michael@8: michael@8: switch (result) { michael@8: case Success_Calendar: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.success_calendar, duration); michael@8: toast.show(); michael@8: finish(); michael@8: break; michael@8: michael@8: case Success_Collection: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.success_collection, duration); michael@8: toast.show(); michael@8: finish(); michael@8: break; michael@8: michael@8: case MalformedURLException: michael@8: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_incorrect_url_format, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_incorrect_url_format)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: case InvalidResponse: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_invalid_server_answer, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_invalid_server_answer)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: case WrongUrl: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_wrong_url, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_wrong_url)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: michael@8: case GeneralSecurityException: michael@8: break; michael@8: case UnkonwnException: michael@8: break; michael@8: case WrongCredentials: michael@8: mPasswordView.setError(getString(R.string.error_incorrect_password)); michael@8: mPasswordView.requestFocus(); michael@8: break; michael@8: michael@8: case ConnectionRefused: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_connection_refused, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_connection_refused)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: case UNTRUSTED_CERT: michael@8: toast = Toast.makeText(getApplicationContext(), getString(R.string.error_untrusted_certificate), duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_ssl)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: case Account_Already_In_Use: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_account_already_in_use, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_account_already_in_use)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: default: michael@8: toast = Toast.makeText(getApplicationContext(), R.string.error_unkown_error, duration); michael@8: toast.show(); michael@8: mURLView.setError(getString(R.string.error_unkown_error)); michael@8: mURLView.requestFocus(); michael@8: break; michael@8: } michael@8: michael@8: michael@8: } michael@8: michael@8: @Override michael@8: protected void onCancelled() { michael@8: mAuthTask = null; michael@8: showProgress(false); michael@8: } michael@8: } michael@9: }