/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package eu.ttbox.androgister.sync.authenticator;
import eu.ttbox.androgister.sync.Constants;
import eu.ttbox.androgister.R;
import eu.ttbox.androgister.sync.client.NetworkUtilities;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.TextView;
/**
* Activity which displays login screen to the user.
* https://sites.google.com/site/andsamples/concept-of-syncadapter-androidcontentabstractthreadedsyncadapter#create_accoun
*/
public class AuthenticatorActivity extends AccountAuthenticatorActivity {
/** The Intent flag to confirm credentials. */
public static final String PARAM_CONFIRM_CREDENTIALS = "confirmCredentials";
/** The Intent extra to store password. */
public static final String PARAM_PASSWORD = "password";
/** The Intent extra to store username. */
public static final String PARAM_USERNAME = "username";
/** The Intent extra to store username. */
public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
/** The tag used to log to adb console. */
private static final String TAG = "AuthenticatorActivity";
private AccountManager mAccountManager;
/** Keep track of the login task so can cancel it if requested */
private UserLoginTask mAuthTask = null;
/** Keep track of the progress dialog so we can dismiss it */
private ProgressDialog mProgressDialog = null;
/**
* If set we are just checking that the user knows their credentials; this
* doesn't cause the user's password or authToken to be changed on the
* device.
*/
private Boolean mConfirmCredentials = false;
/** for posting authentication attempts back to UI thread */
private final Handler mHandler = new Handler();
private TextView mMessage;
private String mPassword;
private EditText mPasswordEdit;
/** Was the original caller asking for an entirely new account? */
protected boolean mRequestNewAccount = false;
private String mUsername;
private EditText mUsernameEdit;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Log.i(TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
mAccountManager = AccountManager.get(this);
Log.i(TAG, "loading data from Intent");
final Intent intent = getIntent();
mUsername = intent.getStringExtra(PARAM_USERNAME);
mRequestNewAccount = mUsername == null;
mConfirmCredentials = intent.getBooleanExtra(PARAM_CONFIRM_CREDENTIALS, false);
Log.i(TAG, " request new: " + mRequestNewAccount);
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.login_activity);
getWindow().setFeatureDrawableResource(
Window.FEATURE_LEFT_ICON, android.R.drawable.ic_dialog_alert);
mMessage = (TextView) findViewById(R.id.message);
mUsernameEdit = (EditText) findViewById(R.id.username_edit);
mPasswordEdit = (EditText) findViewById(R.id.password_edit);
if (!TextUtils.isEmpty(mUsername)) mUsernameEdit.setText(mUsername);
mMessage.setText(getMessage());
}
/*
* {@inheritDoc}
*/
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setMessage(getText(R.string.ui_activity_authenticating));
dialog.setIndeterminate(true);
dialog.setCancelable(true);
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
Log.i(TAG, "user cancelling authentication");
if (mAuthTask != null) {
mAuthTask.cancel(true);
}
}
});
// We save off the progress dialog in a field so that we can dismiss
// it later. We can't just call dismissDialog(0) because the system
// can lose track of our dialog if there's an orientation change.
mProgressDialog = dialog;
return dialog;
}
/**
* Handles onClick event on the Submit button. Sends username/password to
* the server for authentication. The button is configured to call
* handleLogin() in the layout XML.
*
* @param view The Submit button for which this method is invoked
*/
public void handleLogin(View view) {
if (mRequestNewAccount) {
mUsername = mUsernameEdit.getText().toString();
}
mPassword = mPasswordEdit.getText().toString();
if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {
mMessage.setText(getMessage());
} else {
// Show a progress dialog, and kick off a background task to perform
// the user login attempt.
showProgress();
mAuthTask = new UserLoginTask();
mAuthTask.execute();
}
}
/**
* Called when response is received from the server for confirm credentials
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller.
*
* @param result the confirmCredentials result.
*/
private void finishConfirmCredentials(boolean result) {
Log.i(TAG, "finishConfirmCredentials()");
final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
mAccountManager.setPassword(account, mPassword);
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
* Called when response is received from the server for authentication
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller. We store the
* authToken that's returned from the server as the 'password' for this
* account - so we're never storing the user's actual password locally.
*
* @param result the confirmCredentials result.
*/
private void finishLogin(String authToken) {
Log.i(TAG, "finishLogin()");
final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, mPassword, null);
//Configure it to start every 150 seconds
// Bundle params = new Bundle();
// params.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
// params.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false);
// params.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
// ContentResolver.addPeriodicSync(account, ContactsContract.AUTHORITY, params, 150);
// Set contacts sync for this account.
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
} else {
mAccountManager.setPassword(account, mPassword);
}
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
* Called when the authentication process completes (see attemptLogin()).
*
* @param authToken the authentication token returned by the server, or NULL if
* authentication failed.
*/
public void onAuthenticationResult(String authToken) {
boolean success = ((authToken != null) && (authToken.length() > 0));
Log.i(TAG, "onAuthenticationResult(" + success + ")");
// Our task is complete, so clear it out
mAuthTask = null;
// Hide the progress dialog
hideProgress();
if (success) {
if (!mConfirmCredentials) {
finishLogin(authToken);
} else {
finishConfirmCredentials(success);
}
} else {
Log.e(TAG, "onAuthenticationResult: failed to authenticate");
if (mRequestNewAccount) {
// "Please enter a valid username/password.
mMessage.setText(getText(R.string.login_activity_loginfail_text_both));
} else {
// "Please enter a valid password." (Used when the
// account is already in the database but the password
// doesn't work.)
mMessage.setText(getText(R.string.login_activity_loginfail_text_pwonly));
}
}
}
public void onAuthenticationCancel() {
Log.i(TAG, "onAuthenticationCancel()");
// Our task is complete, so clear it out
mAuthTask = null;
// Hide the progress dialog
hideProgress();
}
/**
* Returns the message to be displayed at the top of the login dialog box.
*/
private CharSequence getMessage() {
getString(R.string.label);
if (TextUtils.isEmpty(mUsername)) {
// If no username, then we ask the user to log in using an
// appropriate service.
final CharSequence msg = getText(R.string.login_activity_newaccount_text);
return msg;
}
if (TextUtils.isEmpty(mPassword)) {
// We have an account but no password
return getText(R.string.login_activity_loginfail_text_pwmissing);
}
return null;
}
/**
* Shows the progress UI for a lengthy operation.
*/
private void showProgress() {
showDialog(0);
}
/**
* Hides the progress UI for a lengthy operation.
*/
private void hideProgress() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
/**
* Represents an asynchronous task used to authenticate a user against the
* SampleSync Service
*/
public class UserLoginTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
// We do the actual work of authenticating the user
// in the NetworkUtilities class.
try {
return NetworkUtilities.authenticate(mUsername, mPassword);
} catch (Exception ex) {
Log.e(TAG, "UserLoginTask.doInBackground: failed to authenticate");
Log.i(TAG, ex.toString());
return null;
}
}
@Override
protected void onPostExecute(final String authToken) {
// On a successful authentication, call back into the Activity to
// communicate the authToken (or null for an error).
onAuthenticationResult(authToken);
}
@Override
protected void onCancelled() {
// If the action was canceled (by the user clicking the cancel
// button in the progress dialog), then call back into the
// activity to let it know.
onAuthenticationCancel();
}
}
}