org.elasticdroid.LoginView.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticdroid.LoginView.java

Source

/**
 *  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