/** * This file is part of ElasticDroid. * * ElasticDroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * ElasticDroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with ElasticDroid. If not, see <http://www.gnu.org/licenses/>. * * Authored by Siddhu Warrier on 22 Oct 2010 */ package org.elasticdroid; import static org.elasticdroid.utils.ResultConstants.RESULT_NEW_USER; import java.util.HashMap; import org.apache.commons.httpclient.HttpStatus; import org.elasticdroid.model.LoginModel; import org.elasticdroid.tpl.GenericActivity; import org.elasticdroid.utils.DialogConstants; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.SQLException; import android.os.Bundle; import android.text.Html; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import android.widget.Toast; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; /** * An activity class that inherits from GenericActivity which inherits from Activity. * This is because Java doesn't allow Multiple Inheritance like nice languages like C do! ;) * * @author Siddhu Warrier * * 2 Nov 2010 */ public class LoginView extends GenericActivity implements OnClickListener { /** AWS username. Can be IAM username or AWS email address */ private String username; /** AWS Access key for the username. Checked if it belongs to IAM username entered. But not * checked if not IAM username. **/ private String accessKey; /** AWS Secret Access key for the username. Checked if it belongs to IAM username entered. But * not checked if not IAM username. **/ private String secretAccessKey; /** Reference to loginModel object which does the credential checks and stores user details in * DB*/ private LoginModel loginModel; /** Dialog box for credential verification errors */ private AlertDialog alertDialogBox; /** set to show if alert dialog displayed. Used to decide whether to restore progress dialog * when screen rotated. */ private boolean alertDialogDisplayed; /** message displayed in {@link #alertDialogBox alertDialogBox}.*/ private String alertDialogMessage; /** constant to indicate reason for intent sent to {@link #UserPickerView UserPickerView}. * @see #startUserPicker()*/ private static final int PICK_USERS = 0; /** * Called when the activity is first created or recreated. * */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //restore model if the activity was reloaded in the middle of model processing Object retained = getLastNonConfigurationInstance(); if (retained instanceof LoginModel) { Log.i(this.getClass().getName(), "Reclaiming previous background task."); loginModel = (LoginModel) retained; loginModel.setActivity(this); //tell loginModel that this is the new recreated activity } else { //there was nothing running previously in the background loginModel = null; } //set content view setContentView(R.layout.login); //create the alert dialog alertDialogBox = new AlertDialog.Builder(this).create(); //create alert box to alertDialogBox.setCancelable(false); //add a neutral OK button and set action. alertDialogBox.setButton(this.getString(R.string.loginview_alertdialogbox_button), new DialogInterface.OnClickListener() { //click listener on the alert box - unlock orientation when clicked. //this is to prevent orientation changing when alert box locked. public void onClick(DialogInterface arg0, int arg1) { alertDialogDisplayed = false; alertDialogBox.dismiss(); } }); View loginButton = findViewById(R.id.loginButton);//set action listeners for the buttons loginButton.setOnClickListener(this);//this class will listen to the login buttons } /** * Re-launches a dialog box if necessary. * * This derives from the Android lifecycle. When an Activity is destroyed and recreated, * this is the order of calls made: * <ul> * <li> onSaveInstanceState() * <li> onDestroy() * <li> {@link #onCreate() onCreate()} * <li> onStart() * <li> {@link #onRestoreInstanceState(Bundle)} * <li> {@link #onResume()} * * So it is only in {@link #onRestoreInstanceState(Bundle)} that we have restored the values * of alertDialogDisplayed and alertDialogMessage. So if we need to re-display it, we should * override onResume() as well. */ @Override public void onResume(){ super.onResume(); //call base class method //check if an alert dialog was displayed before //remember: booleans are false by default and will be set to true //only if this object was destroyed and recreated when a dialog was displyaed. Log.v("onCreate()", "Recreating dialog box if necessary..."); if (alertDialogDisplayed) { alertDialogBox.setMessage(alertDialogMessage); alertDialogBox.show(); } else if ( (username == null) && (accessKey == null) && (secretAccessKey == null)) { startUserPicker(); } } /** * Convenience method to start the userpicker. */ private void startUserPicker() { Intent userPickerIntent = new Intent(); userPickerIntent.setClassName("org.elasticdroid", "org.elasticdroid.UserPickerView"); startActivityForResult(userPickerIntent, PICK_USERS); } /** * @brief Handles the event of the login button being clicked. * */ @Override public void onClick(View buttonClicked) { //if the data passes basic checks, then try accessing AWS if (validateLoginDetails()) { loginModel = new LoginModel(this); loginModel.execute(username, accessKey, secretAccessKey); } } /** * @brief Private method to perform basic validity checks on the credentials entered. * * @return false if any of the fields isn't filled, or the email address is invalid. * true otherwise * */ private boolean validateLoginDetails() { /* * define local variables to hold the username, access key info from the UI. * This is for convenience mostly, and to avoid having to perform findViewById * lookups multiple times */ EditText editTextUsername = (EditText)findViewById(R.id.usernameEntry); EditText editTextAccessKey = (EditText)findViewById(R.id.akEntry); EditText editTextSecretAccessKey = (EditText)findViewById(R.id.sakEntry); //get the strings from the username. username = editTextUsername.getText().toString(); //get the username accessKey = editTextAccessKey.getText().toString(); //get the access key secretAccessKey = editTextSecretAccessKey.getText().toString(); //get the secret access key Log.v(this.getClass().getName(), "User name:" + username); //if any of username, access key, or secret access key is blank, return false //and highlight the appropr. EditText box if (username.trim().equals("")) { editTextUsername.setError(this.getString(R.string.loginview_username_empty_err)); editTextUsername.requestFocus(); //return false to the click handler, so it doesn't try to login return false; } else if (accessKey.trim().equals("")) { editTextAccessKey.setError(this.getString(R.string.loginview_accesskey_empty_err)); editTextAccessKey.requestFocus(); //return false to the click handler, so it doesn't try to login return false; } else if (secretAccessKey.trim().equals("")) { editTextSecretAccessKey.setError(this.getString(R.string.loginview_secretaccesskey_empty_err)); editTextSecretAccessKey.requestFocus(); //set the focus editTextSecretAccessKey.requestFocus(); //return false to the click handler, so it doesn't try to login return false; } //if all of the validation checks succeeded, check with the model. return true; } /** * Process results from model. Called by onPostExecute() method * in any given Model class. * * Displays either an error message (if result is an exeception) * or the next activity. * * Overrides * @see org.elasticdroid.tpl.GenericActivity#processModelResults(java.lang.Object) */ @Override public void processModelResults(Object result) { Log.v(this.getClass().getName(), "Processing model results..."); //dismiss the progress bar if (progressDialogDisplayed) { progressDialogDisplayed = false; dismissDialog(DialogConstants.PROGRESS_DIALOG.ordinal()); } if (result == null) { Toast.makeText(this, Html.fromHtml(this.getString(R.string.cancelled_login)), Toast. LENGTH_LONG).show(); return; //do not execute the rest of this method. } /* * The result returned by the model can be: * a) true: if authentication successful. * b) AmazonServiceException: if authentication failed (typically). * c) AmazonClientException: if communication to AWS failed (user not connected to internet?). * d) null: if the credentials have been validated. */ if (result instanceof Boolean) { HashMap<String, String> connectionData = new HashMap<String, String>(); //TODO add the ability to change the default dashboard for a user finish(); //finish the activity; we dont want the user to be able to return to this screen using the //back key. Intent displayDashboardIntent = new Intent(); displayDashboardIntent.setClassName("org.elasticdroid", "org.elasticdroid.EC2DashboardView"); //pass the username, access key, and secret access key to the dashboard as arguments //create a HashMap<String,String> to hold the connection data connectionData.put("username",username); connectionData.put("accessKey", accessKey); connectionData.put("secretAccessKey", secretAccessKey); //add connection data to intent, and start new activity displayDashboardIntent.putExtra("org.elasticdroid.LoginView.connectionData", connectionData); startActivity(displayDashboardIntent); } else if (result instanceof AmazonServiceException) { if ((((AmazonServiceException)result).getStatusCode() == HttpStatus.SC_UNAUTHORIZED) || (((AmazonServiceException)result).getStatusCode() == HttpStatus.SC_FORBIDDEN)) { //set errors in the access key and secret access key fields. ((EditText)findViewById(R.id.akEntry)).setError(this.getString(R.string. loginview_invalid_credentials_err)); ((EditText)findViewById(R.id.sakEntry)).setError(this.getString(R.string. loginview_invalid_credentials_err)); alertDialogMessage = this.getString(R.string.loginview_invalid_keys_dlg); } else { //TODO a wrong SecretAccessKey is handled using a different error if the AccessKey is right. //Handle this. alertDialogMessage = this.getString(R.string.loginview_unexpected_err_dlg) + ((AmazonServiceException)result).getStatusCode() + "--" + ((AmazonServiceException)result).getMessage()+ ". " + this.getString(R.string.loginview_bug_report_dlg); } //whatever the error, display the error //and set the boolean to true. This is so that we know we should redisplay //dialog on restore. Log.e(this.getClass().getName(), alertDialogMessage); alertDialogDisplayed = true; } else if (result instanceof AmazonClientException) { alertDialogMessage = this.getString(R.string.loginview_no_connxn_dlg); Log.e(this.getClass().getName(), alertDialogMessage); alertDialogDisplayed = true; } else if (result instanceof IllegalArgumentException) { ((EditText)findViewById(R.id.usernameEntry)).setError(this.getString (R.string.loginview_invalid_username_err)); alertDialogMessage = this.getString(R.string.loginview_invalid_username_err); Log.e(this.getClass().getName(), alertDialogMessage); alertDialogDisplayed = true; } else if (result instanceof SQLException) { alertDialogMessage = this.getString(R.string.loginview_username_exists_dlg); Log.e(this.getClass().getName(), alertDialogMessage); alertDialogDisplayed = true; } else if (result != null) { Log.e(this.getClass().getName(), "Unexpected error!!!"); } //set the loginModel to null loginModel = null; //display the alert dialog if the user set the displayed var to true if (alertDialogDisplayed) { alertDialogBox.setMessage(alertDialogMessage); alertDialogBox.show();//show error } } /** * Save reference to {@link org.elasticdroid.model.LoginModel Async Task * when object is destroyed (for instance when screen rotated). * * This has to be done as the Async Task is running in the background. */ @Override public Object onRetainNonConfigurationInstance() { Log.v(this.getClass().getName(), "Object about to destroyed..."); //if the model is being executed when the onDestroy method is called. if (loginModel != null) { loginModel.setActivityNull(); return loginModel; } return null; } /** * Saves data to restore when activity recreated. * * Saves the state of {@link #alertDialogBox}, {@link #username}, {@link #accessKey} and * {@link #secretAccessKey} */ @Override public void onSaveInstanceState(Bundle saveState) { Log.v(this.getClass().getName(), "Save instance state..."); /* * define local variables to hold the username, access key info from the UI. * This is for convenience mostly, and to avoid having to perform findViewById * lookups multiple times */ EditText editTextUsername = (EditText)findViewById(R.id.usernameEntry); EditText editTextAccessKey = (EditText)findViewById(R.id.akEntry); EditText editTextSecretAccessKey = (EditText)findViewById(R.id.sakEntry); //get the strings from the username. username = editTextUsername.getText().toString(); //get the username accessKey = editTextAccessKey.getText().toString(); //get the access key secretAccessKey = editTextSecretAccessKey.getText().toString(); //get the secret access key //if a dialog is displayed when this happens, dismiss it if (alertDialogDisplayed) { alertDialogBox.dismiss(); } saveState.putBoolean("alertDialogDisplayed", alertDialogDisplayed); saveState.putString("alertDialogMessage", alertDialogMessage); saveState.putString("username", username); saveState.putString("accessKey", accessKey); saveState.putString("secretAccessKey", secretAccessKey); //call the superclass (Activity)'s save state method super.onSaveInstanceState(saveState); } /** * Method to: * <ul> * <li> Restore {@link #alertDialogMessage} and {@link #alertDialogDisplayed}</li> * <li> Restore {@link #username}, {@link #accessKey}, and {@link #secretAccessKey}</li> * </ul> * * The data restored here is used in {@link #onResume()} to decide whether to redisplay dialog * and what to set in the recreated text fields, when the screen is rotated (for example). */ @Override public void onRestoreInstanceState(Bundle stateToRestore) { super.onRestoreInstanceState(stateToRestore); Log.v(this.getClass().getName(), "Restore instance state..."); alertDialogDisplayed = stateToRestore.getBoolean("alertDialogDisplayed"); Log.v(this.getClass().getName(), "alertDialogDisplayed = " + alertDialogDisplayed); alertDialogMessage = stateToRestore.getString("alertDialogMessage"); username = stateToRestore.getString("username"); accessKey = stateToRestore.getString("accessKey"); secretAccessKey = stateToRestore.getString("secretAccessKey"); } /** * Get the results of the userpicker and handle it appropriately. The results can be: * <ul> * <li> RESULT_CANCELLED: User pressed back button. Finish this activity too.</li> * <li> RESULT_OK:</li> * <ul> * <li> SELECTION_SIZE == 0: no pre-defined users. Ask user to enter data.</li> * <li> SELECTION_SIZE == 1: populate fields appropriately, Log the user in.</li> * </ul> * <li> {@link org.elasticdroid.utils.ResultConstants#RESULT_NEW_USER} * </ul> * * @see {@link http://www.brighthub.com/mobile/google-android/articles/40317.aspx#ixzz14dQdmgrG(non-Javadoc) * this article for more} * @see android.app.Activity#onActivityResult(int, int, android.content.Intent) */ @Override protected void onActivityResult(int requestCode, int resultCode,Intent data) { Log.v(this.getClass().getName(), "Subactivity returned with result: " + resultCode); EditText editTextUsername = (EditText)findViewById(R.id.usernameEntry); EditText editTextAccessKey = (EditText)findViewById(R.id.akEntry); EditText editTextSecretAccessKey = (EditText)findViewById(R.id.sakEntry); switch(resultCode) { case RESULT_CANCELED: //user pressed back button finish(); break; case RESULT_OK: //if selection size is 0, there is no login data. if (data.getIntExtra("SELECTION_SIZE", 1) == 0) { Log.v(this.getClass().getName(), "No pre-existing user data."); } //there is some login data the user has selected. Call model to verify //and start up ElasticDroid proper. else { //set the new data to the View. editTextUsername.setText(data.getStringExtra("USERNAME")); editTextAccessKey.setText(data.getStringExtra("ACCESS_KEY")); editTextSecretAccessKey.setText(data.getStringExtra("SECRET_ACCESS_KEY")); //if the data passes basic checks, then try accessing AWS if (validateLoginDetails()) { loginModel = new LoginModel(this); loginModel.execute(username, accessKey, secretAccessKey); } } break; case RESULT_NEW_USER: //Allow user to enter new username. Clear text fields and username details. username = ""; accessKey = ""; secretAccessKey = ""; editTextUsername.setText(""); editTextAccessKey.setText(""); editTextSecretAccessKey.setText(""); break; } } /** * Overridden method to display the menu on press of the menu key * * Inflates and displays main menu. */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.login_menu, menu); return true; } /** * Overriden method to handle selection of menu item */ @Override public boolean onOptionsItemSelected(MenuItem selectedItem) { switch (selectedItem.getItemId()) { case R.id.login_menuitem_another_user: startUserPicker(); return true; case R.id.login_menuitem_about: Intent aboutIntent = new Intent(this, AboutView.class); startActivity(aboutIntent); return true; default: return super.onOptionsItemSelected(selectedItem); } } /** * Handle cancel of progress dialog * @see android.content.DialogInterface.OnCancelListener#onCancel(android.content. * DialogInterface) */ @Override public void onCancel(DialogInterface dialog) { //this cannot be called UNLESS the user has the model running. //i.e. the prog bar is visible Log.v(this.getClass().getName(), "Activity: Cancelled!"); loginModel.cancel(true); } }//end of class