package com.nuscomputing.ivle; import com.nuscomputing.ivlelapi.FailedLoginException; import com.nuscomputing.ivlelapi.IVLE; import com.nuscomputing.ivlelapi.JSONParserException; import com.nuscomputing.ivlelapi.NetworkErrorException; import android.accounts.Account; import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountManager; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.Toast; /** * UI to authenticate. * @author yjwong */ public class AuthenticatorActivity extends AccountAuthenticatorActivity { // {{{ properties /** TAG for logging */ public static final String TAG = "AuthenticatorActivity"; /** Intent extra: Stores username */ public static final String PARAM_USERNAME = "username"; /** Intent extra: Stores authentication token type */ public static final String PARAM_AUTHTOKEN_TYPE = "authTokenType"; /** Intent flag: Confirm credentials */ public static final String PARAM_CONFIRM_CREDENTIALS = "confirmCredentials"; /** Account Manager */ private AccountManager mAccountManager; /** Keep track of the login task to we can cancel it when requested */ private AuthenticationTask mAuthTask = null; /** Progress dialog to notify user of login progress */ private ProgressDialog mProgressDialog = null; /** Alert dialog for various purposes */ private AlertDialog mAlertDialog = null; /** Was the original caller asking for an entirely new account? */ protected boolean mRequestNewAccount = false; /** Username */ private String mUsername; /** Password */ private String mPassword; /** Username edit field */ private EditText mUsernameEdit; /** Password edit field */ private EditText mPasswordEdit; /** Connection state */ protected boolean mHasConnection = true; /** * 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; // }}} // {{{ methods /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { // Call the superclass. super.onCreate(savedInstanceState); // Obtain an instance of the account manager. mAccountManager = AccountManager.get(this); final Intent intent = getIntent(); mUsername = intent.getStringExtra(PARAM_USERNAME); mRequestNewAccount = (mUsername == null); mConfirmCredentials = intent.getBooleanExtra(PARAM_CONFIRM_CREDENTIALS, false); // Display the UI. setContentView(R.layout.authenticator_activity); mUsernameEdit = (EditText) findViewById(R.id.username); mPasswordEdit = (EditText) findViewById(R.id.password); if (!TextUtils.isEmpty(mUsername)) { mUsernameEdit.setText(mUsername); } } /** * Method: onPause * Part of the Android activity lifecycle. */ @Override public void onPause() { // Call the superclass. super.onPause(); // If we have any currently opened alert dialogs, dismiss. Log.v(TAG, "onPause: mAlertDialog = " + mAlertDialog); if (mAlertDialog != null) { mAlertDialog.dismiss(); } } /** * Method: onResume * Part of the Android activity lifecycle. */ @Override public void onResume() { // Call the superclass. super.onResume(); // If we have any previously opened alert dialogs, show. Log.v(TAG, "onResume: mAlertDialog = " + mAlertDialog); if (mAlertDialog != null) { mAlertDialog.show(); } } /** * Method: handleSignIn * Handles onClick event on the "Sign In" button. Sends username/password * to the server for authentication. The button is confirmed to call * handleSignIn() in the XML. * * @param view */ public void handleSignIn(View view) { // Obtain user credentials from form fields. Log.d(TAG, "handleSignIn: handling sign in"); // Validate form fields. if (mRequestNewAccount) { mUsername = mUsernameEdit.getText().toString(); } if (TextUtils.isEmpty(mUsername)) { final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setMessage(getText(R.string.authenticator_activity_enter_nusnet_id)); dialog.setButton(AlertDialog.BUTTON_NEUTRAL, getText(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); dialog.show(); return; } mPassword = mPasswordEdit.getText().toString(); if (TextUtils.isEmpty(mPassword)) { final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setMessage(getText(R.string.authenticator_activity_enter_password)); dialog.setButton(AlertDialog.BUTTON_NEUTRAL, getText(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); dialog.show(); return; } // Show progress dialog, and kick off a background task to perform // the user login attempt. showProgress(); mAuthTask = new AuthenticationTask(); mAuthTask.execute(); } /** * Method: handleCancel * Handles onClick event on the "Cancel" button. * * @param view */ public void handleCancel(View view) { setResult(RESULT_CANCELED); finish(); } /** * Method: showProgress * Shows a progress dialog to indicate login activity. */ public void showProgress() { final ProgressDialog dialog = new ProgressDialog(this); dialog.setMessage(getText(R.string.signing_in)); dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); dialog.setIndeterminate(true); dialog.setCancelable(true); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (mAuthTask != null) { mAuthTask.cancel(true); } } }); mProgressDialog = dialog; dialog.show(); } /** * Method: hideProgress * Hides the progress dialog. */ public void hideProgress() { if (mProgressDialog != null) { mProgressDialog.dismiss(); mProgressDialog = null; } } /** * Method: onAuthenticationCancel * Called when the authentication process is cancelled. */ public void onAuthenticationCancel() { // Our task is complete, so clear it out. mAuthTask = null; // Hide the progress dialog. hideProgress(); } /** * Method: onAuthenticationResult * Called when the authentication process completes. * * @param authToken */ public void onAuthenticationResult(String authToken) { // Check result of authentication. Log.v(TAG, "onAuthenticationResult: authToken = " + authToken); boolean success = ((authToken != null) && (authToken.length() > 0)); // Our task is complete, so clear it out. mAuthTask = null; // Hide the progress dialog. hideProgress(); // Inform the invoker about the result. if (success) { if (!mConfirmCredentials) { finishLogin(authToken); } else { finishConfirmCredentials(success); } } else { AlertDialog dialog = new AlertDialog.Builder(this).create(); if (mHasConnection) { if (mRequestNewAccount) { dialog.setMessage(getText(R.string.authenticator_activity_nusnet_id_or_password_invalid)); } else { // Used when the account is already in the database but the // password doesn't work. dialog.setMessage(getText(R.string.authenticator_activity_password_invalid)); } } else { dialog.setMessage(getText(R.string.authenticator_activity_problem_communicating_with_server)); } // Set button parameters and show the dialog. dialog.setButton(AlertDialog.BUTTON_NEUTRAL, getText(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); mAlertDialog = null; } }); dialog.show(); mAlertDialog = dialog; } } /** * Method: finishLogin * 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 */ private void finishLogin(String authToken) { final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE); if (mRequestNewAccount) { mAccountManager.addAccountExplicitly(account, mPassword, null); } else { mAccountManager.setPassword(account, mPassword); } // Send a toast notification. Context context = getApplicationContext(); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(context, getText(R.string.authenticator_activity_account_successfully_added), duration); toast.show(); // Activate sync automatically. // Hack: set the syncable state to be 0 so that we can perform // transactional changes to the sync configuration. Resources res = getResources(); ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 0); ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); ContentResolver.addPeriodicSync(account, Constants.PROVIDER_AUTHORITY, new Bundle(), res.getInteger(R.integer.default_sync_interval) * 60 * 60); ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); IVLEUtils.requestSyncNow(account); // Return to the caller. final Intent intent = new Intent(); intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername); intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE); setAccountAuthenticatorResult(intent.getExtras()); // Okay, done. setResult(RESULT_OK, intent); finish(); } /** * Method: finishConfirmCredentials * 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 */ private void finishConfirmCredentials(boolean result) { // Set the password. final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE); mAccountManager.setPassword(account, mPassword); // Return to the caller. final Intent intent = new Intent(); intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result); setAccountAuthenticatorResult(intent.getExtras()); setResult(RESULT_OK, intent); finish(); } // }}} // {{{ classes /** * An asynchronous task to authenticate the user. * @author yjwong */ public class AuthenticationTask extends AsyncTask<Void, Void, String> { // {{{ methods @Override protected String doInBackground(Void... params) { Log.v(TAG, "AuthenticationTask: started"); mHasConnection = true; try { IVLE ivle; ivle = new IVLE(Constants.API_KEY, mUsername, mPassword); return ivle.authToken; } catch (FailedLoginException e) { Log.v(TAG, "AuthenticationTask: got FailedLoginException"); return null; } catch (NetworkErrorException e) { Log.v(TAG, "AuthenticationTask: got NetworkErrorException"); mHasConnection = false; return null; } catch (JSONParserException e) { Log.v(TAG, "AuthenticationTask: got XMLParserException"); throw new IllegalStateException(); } } @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 cancelled (by the user touching the cancel // button in the progress dialog), then call back into the activity // to let it know. onAuthenticationCancel(); } // }}} } // }}} }