org.ohmage.authenticator.AuthenticatorActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.ohmage.authenticator.AuthenticatorActivity.java

Source

/*
 * 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 org.ohmage.authenticator;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.URLUtil;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

import org.ohmage.AccountHelper;
import org.ohmage.ConfigHelper;
import org.ohmage.NotificationHelper;
import org.ohmage.OhmageApi.AuthenticateResponse;
import org.ohmage.OhmageApi.CampaignReadResponse;
import org.ohmage.OhmageApplication;
import org.ohmage.PreferenceStore;
import org.ohmage.UserPreferencesHelper;
import org.ohmage.Utilities;
import org.ohmage.activity.DashboardActivity;
import org.ohmage.activity.PasswordChangeActivity;
import org.ohmage.async.CampaignReadTask;
import org.ohmage.db.DbContract;
import org.ohmage.db.Models.Campaign;
import org.ohmage.db.utils.Lists;
import org.ohmage.library.R;
import org.ohmage.logprobe.Analytics;
import org.ohmage.logprobe.LogProbe.Status;

import java.net.URI;
import java.util.ArrayList;

/**
 * Activity which displays login screen to the user.
 */
public class AuthenticatorActivity extends AccountAuthenticatorFragmentActivity
        implements LoaderCallbacks<CampaignReadResponse> {
    private static final String TAG = "AuthenticatorActivity";

    private static final int LOGIN_FINISHED = 0;
    private static final int PASSWORD_CHANGE = 1;

    private static final int DIALOG_LOGIN_ERROR = 1;
    private static final int DIALOG_NETWORK_ERROR = 2;
    private static final int DIALOG_LOGIN_PROGRESS = 3;
    private static final int DIALOG_INTERNAL_ERROR = 4;
    private static final int DIALOG_USER_DISABLED = 5;
    private static final int DIALOG_DOWNLOADING_CAMPAIGNS = 6;
    private static final int DIALOG_SERVER_LIST = 7;

    public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials";
    public static final String PARAM_PASSWORD = "password";

    /**
     * The {@link AuthenticatorActivity} looks for this extra to determine if it
     * should update the credentials for the user
     */
    public static final String PARAM_USERNAME = "username";
    public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";

    private static final String KEY_OHMAGE_SERVER = "key_ohmage_server";

    private AccountManager mAccountManager;
    private Thread mAuthThread;
    private String mAuthtoken;
    private String mAuthtokenType;

    /**
     * If set we are just checking that the user knows their credentials; this
     * doesn't cause the user's password 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;

    private EditText mServerEdit;
    private PreferenceStore mPreferencesHelper;
    private String mHashedPassword;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        mAccountManager = AccountManager.get(this);

        final Intent intent = getIntent();
        mUsername = intent.getStringExtra(PARAM_USERNAME);
        mAuthtokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);

        // If we are just logging in regularly, we need to set the authtoken
        // type
        if (mAuthtokenType == null) {
            mAuthtokenType = OhmageApplication.AUTHTOKEN_TYPE;
        }

        mRequestNewAccount = mUsername == null;
        mConfirmCredentials = intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS, false);

        mPreferencesHelper = new PreferenceStore(this);

        if (mPreferencesHelper.isUserDisabled()) {
            ((OhmageApplication) getApplication()).resetAll();
        }

        // if they are, redirect them to the dashboard
        if (AccountHelper.accountExists() && !mConfirmCredentials) {
            startActivityForResult(new Intent(this, DashboardActivity.class), LOGIN_FINISHED);
            return;
        }

        setContentView(R.layout.login);

        mMessage = (TextView) findViewById(R.id.version);
        mUsernameEdit = (EditText) findViewById(R.id.login_username);
        mPasswordEdit = (EditText) findViewById(R.id.login_password);
        mServerEdit = (EditText) findViewById(R.id.login_server_edit);

        if (mConfirmCredentials) {
            mUsernameEdit.setEnabled(false);
            mPasswordEdit.requestFocus();
        }

        mUsernameEdit.setText(mUsername);
        mMessage.setText(getVersion());

        boolean showRegisterLink = getResources().getBoolean(R.bool.allow_user_registration);
        TextView registerAccountLink = (TextView) findViewById(R.id.login_register_new_account);
        registerAccountLink.setVisibility(showRegisterLink ? View.VISIBLE : View.GONE);
        registerAccountLink.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // reads the currently selected server and fires a browser
                // intent which takes the user to the registration page for that
                // server
                if (ensureServerUrl()) {
                    // use the textbox to make a url
                    String url = mServerEdit.getText().toString().split(" ")[0] + "#register";
                    Intent i = new Intent(Intent.ACTION_VIEW);
                    i.setData(Uri.parse(url));
                    startActivity(i);
                } else
                    Toast.makeText(v.getContext(), R.string.login_invalid_server, Toast.LENGTH_SHORT).show();
            }
        });

        if (getResources().getBoolean(R.bool.allow_custom_server)) {
            View serverContainer = findViewById(R.id.login_server_container);
            serverContainer.setVisibility(View.VISIBLE);
        }

        String defaultServer = ConfigHelper.serverUrl();
        if (TextUtils.isEmpty(defaultServer))
            defaultServer = getResources().getStringArray(R.array.servers)[0];
        mServerEdit.setText(defaultServer);
        ensureServerUrl();
        mServerEdit.setOnFocusChangeListener(new View.OnFocusChangeListener() {

            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    ensureServerUrl();
                }
            }
        });

        ImageButton addServer = (ImageButton) findViewById(R.id.login_add_server);
        addServer.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                ensureServerUrl();
                showDialog(DIALOG_SERVER_LIST);
            }
        });
    }

    @Override
    protected void onPause() {
        super.onPause();
        Analytics.activity(this, Status.OFF);
    }

    @Override
    public void onResume() {
        super.onResume();
        Analytics.activity(this, Status.ON);

        // Hide any notifications since we started the login activity
        NotificationHelper.hideAuthNotification(this);
        NotificationHelper.hideAccountDisabledNotification(this);
    }

    private CharSequence getVersion() {
        try {
            return "v" + Utilities.getVersion(this);
        } catch (Exception e) {
            Log.e(TAG, "unable to retrieve version", e);
            return null;
        }
    }

    /**
     * The easiest way to make sure the progress dialog is hidden when it is
     * supposed to be is to have a static reference to it...
     */
    private static ProgressDialog pDialog;

    @Override
    public void onDestroy() {
        super.onDestroy();
        pDialog = null;

        getSupportLoaderManager().destroyLoader(0);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog = super.onCreateDialog(id);
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
        switch (id) {

        case DIALOG_LOGIN_ERROR:
            dialogBuilder.setTitle(R.string.login_error).setMessage(R.string.login_auth_error).setCancelable(true)
                    .setPositiveButton(R.string.ok, null)
            /*
             * .setNeutralButton("Help", new
             * DialogInterface.OnClickListener() {
             * @Override public void onClick(DialogInterface dialog, int
             * which) { startActivity(new Intent(LoginActivity.this,
             * HelpActivity.class)); //put extras for specific help on login
             * error } })
             */;
            // add button for contact
            dialog = dialogBuilder.create();
            break;

        case DIALOG_USER_DISABLED:
            dialogBuilder.setTitle(R.string.login_error).setMessage(R.string.login_account_disabled)
                    .setCancelable(true).setPositiveButton(R.string.ok, null)
            /*
             * .setNeutralButton("Help", new
             * DialogInterface.OnClickListener() {
             * @Override public void onClick(DialogInterface dialog, int
             * which) { startActivity(new Intent(LoginActivity.this,
             * HelpActivity.class)); //put extras for specific help on login
             * error } })
             */;
            // add button for contact
            dialog = dialogBuilder.create();
            break;

        case DIALOG_NETWORK_ERROR:
            dialogBuilder.setTitle(R.string.login_error).setMessage(R.string.login_network_error)
                    .setCancelable(true).setPositiveButton(R.string.ok, null)
            /*
             * .setNeutralButton("Help", new
             * DialogInterface.OnClickListener() {
             * @Override public void onClick(DialogInterface dialog, int
             * which) { startActivity(new Intent(LoginActivity.this,
             * HelpActivity.class)); //put extras for specific help on http
             * error } })
             */;
            // add button for contact
            dialog = dialogBuilder.create();
            break;

        case DIALOG_INTERNAL_ERROR:
            dialogBuilder.setTitle(R.string.login_error).setMessage(R.string.login_server_error).setCancelable(true)
                    .setPositiveButton(R.string.ok, null)
            /*
             * .setNeutralButton("Help", new
             * DialogInterface.OnClickListener() {
             * @Override public void onClick(DialogInterface dialog, int
             * which) { startActivity(new Intent(LoginActivity.this,
             * HelpActivity.class)); //put extras for specific help on http
             * error } })
             */;
            // add button for contact
            dialog = dialogBuilder.create();
            break;

        case DIALOG_LOGIN_PROGRESS: {
            pDialog = new ProgressDialog(this);
            pDialog.setMessage(getString(R.string.login_authenticating, getString(R.string.server_name)));
            pDialog.setIndeterminate(true);
            pDialog.setCancelable(false);
            dialog = pDialog;
            break;
        }
        case DIALOG_DOWNLOADING_CAMPAIGNS: {
            ProgressDialog pDialog = new ProgressDialog(this);
            pDialog.setMessage(getString(R.string.login_download_campaign));
            pDialog.setCancelable(false);
            // pDialog.setIndeterminate(true);
            dialog = pDialog;
            break;
        }
        case DIALOG_SERVER_LIST: {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            ArrayList<String> servers = Lists.newArrayList(getResources().getStringArray(R.array.servers));

            if (OhmageApplication.DEBUG_BUILD) {
                servers.add("https://test.ohmage.org/");
            }

            final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.simple_list_item_1,
                    servers);

            builder.setTitle(R.string.login_choose_server);
            builder.setAdapter(adapter, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mServerEdit
                            .setText(((AlertDialog) dialog).getListView().getAdapter().getItem(which).toString());
                }
            });

            dialog = builder.create();
            break;
        }
        }

        return dialog;
    }

    /**
     * Handles onClick event on the Submit button. Sends username/password to
     * the server for authentication.
     * 
     * @param view The Submit button for which this method is invoked
     */
    public void handleLogin(View view) {
        Analytics.widget(view);

        if (!ensureServerUrl()) {
            Toast.makeText(this, R.string.login_invalid_server, Toast.LENGTH_SHORT).show();
            return;
        }

        String server = mServerEdit.getText().toString();
        ConfigHelper.setServerUrl(server.split("\\(")[0].trim());
        ((OhmageApplication) getApplication()).updateLogLevel();

        if (mRequestNewAccount) {
            mUsername = mUsernameEdit.getText().toString();
        }
        mPassword = mPasswordEdit.getText().toString();
        if (!TextUtils.isEmpty(mUsername) && !TextUtils.isEmpty(mPassword)) {
            showDialog(DIALOG_LOGIN_PROGRESS);
            // Start authenticating...
            mAuthThread = AuthenticationUtilities.attemptAuth(mUsername, mPassword, mHandler,
                    AuthenticatorActivity.this);
        }
    }

    /**
     * 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 the confirmCredentials result.
     */
    protected void finishConfirmCredentials(boolean result) {
        Log.v(TAG, "finishConfirmCredentials()");
        final Account account = new Account(mUsername, OhmageApplication.ACCOUNT_TYPE);
        mAccountManager.setPassword(account, mPassword);
        if (mAuthtokenType != null && mAuthtokenType.equals(OhmageApplication.AUTHTOKEN_TYPE)) {
            mAccountManager.setAuthToken(account, mAuthtokenType, mHashedPassword);
        }
        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. Also sets
     * the authToken in AccountManager for this account.
     * 
     * @param the confirmCredentials result.
     */

    protected void createAccount() {
        Log.v(TAG, "finishLogin()");
        final Account account = new Account(mUsername, OhmageApplication.ACCOUNT_TYPE);
        Bundle userData = new Bundle();
        userData.putString(KEY_OHMAGE_SERVER, ConfigHelper.serverUrl());
        mAuthtoken = mHashedPassword;

        if (TextUtils.isEmpty(mAuthtoken)) {
            Log.w(TAG, "Trying to create account with empty password");
            return;
        }

        if (mRequestNewAccount) {
            mAccountManager.addAccountExplicitly(account, mPassword, userData);
            mAccountManager.setAuthToken(account, OhmageApplication.AUTHTOKEN_TYPE, mAuthtoken);
            // Set sync for this account.
            ContentResolver.setIsSyncable(account, DbContract.CONTENT_AUTHORITY, 1);
            ContentResolver.setSyncAutomatically(account, DbContract.CONTENT_AUTHORITY, true);
            ContentResolver.addPeriodicSync(account, DbContract.CONTENT_AUTHORITY, new Bundle(), 3600);
        } else {
            mAccountManager.setPassword(account, mPassword);
        }
        final Intent intent = new Intent();
        intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
        intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, OhmageApplication.ACCOUNT_TYPE);
        if (mAuthtokenType != null && mAuthtokenType.equals(OhmageApplication.AUTHTOKEN_TYPE)) {
            intent.putExtra(AccountManager.KEY_AUTHTOKEN, mAuthtoken);
        }
        setAccountAuthenticatorResult(intent.getExtras());
        setResult(RESULT_OK, intent);

        mPreferencesHelper.edit().putLoginTimestamp(System.currentTimeMillis()).commit();

        if (UserPreferencesHelper.isSingleCampaignMode()) {
            // Download the single campaign
            showDialog(DIALOG_DOWNLOADING_CAMPAIGNS);
            getSupportLoaderManager().restartLoader(0, null, this);
        } else {
            finishLogin();
        }
    }

    protected void finishLogin() {
        if (mConfirmCredentials)
            finish();
        else
            startActivityForResult(new Intent(this, DashboardActivity.class), LOGIN_FINISHED);
    }

    /**
     * Called when the authentication process completes (see attemptLogin()).
     */
    public void onAuthenticationResult(AuthenticateResponse response) {

        try {
            dismissDialog(DIALOG_LOGIN_PROGRESS);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Attempting to dismiss dialog that had not been shown.");
            e.printStackTrace();
            if (pDialog != null)
                pDialog.dismiss();
        }

        switch (response.getResult()) {
        case SUCCESS:
            Log.v(TAG, "login success");
            mHashedPassword = response.getHashedPassword();
            if (!mConfirmCredentials) {
                createAccount();
            } else {
                finishConfirmCredentials(true);
            }
            break;
        case FAILURE:
            if (response.getErrorCodes().contains("0202")) {
                Log.v(TAG, "password change required");
                Intent intent = new Intent(this, PasswordChangeActivity.class);
                intent.putExtra(PasswordChangeActivity.ACCOUNT_NAME, mUsername);
                intent.putExtra(PasswordChangeActivity.OLD_PASSWORD, mPassword);
                startActivityForResult(intent, PASSWORD_CHANGE);
                break;
            } else {
                Log.e(TAG, "login failure: " + response.getErrorCodes());
            }

            // show error dialog
            if (response.getErrorCodes().contains("0201")) {
                mPreferencesHelper.edit().setUserDisabled(true).commit();
                showDialog(DIALOG_USER_DISABLED);
            } else {
                showDialog(DIALOG_LOGIN_ERROR);
            }
            break;
        case HTTP_ERROR:
            Log.w(TAG, "login http error");

            // show error dialog
            showDialog(DIALOG_NETWORK_ERROR);
            break;
        case INTERNAL_ERROR:
            Log.e(TAG, "login internal error");

            // show error dialog
            showDialog(DIALOG_INTERNAL_ERROR);
            break;
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case LOGIN_FINISHED:
            finish();
            break;
        case PASSWORD_CHANGE:
            if (resultCode == RESULT_OK && data != null && data.hasExtra(PasswordChangeActivity.NEW_PASSWORD)) {
                mHashedPassword = data.getStringExtra(PasswordChangeActivity.NEW_PASSWORD);
                createAccount();
            } else {
                Toast.makeText(this, R.string.change_password_unsuccessful, Toast.LENGTH_SHORT).show();
            }
            break;
        default:
            this.onActivityResult(requestCode, resultCode, data);
        }
    }

    /**
     * Ensures that the server url provided is valid. Once it is made valid, it
     * is set as the server url.
     * 
     * @return
     */
    private boolean ensureServerUrl() {
        String text = mServerEdit.getText().toString();

        if (TextUtils.isEmpty(text))
            return false;

        // Assume they want https by default
        URI uri = URI.create(text.split(" ")[0]);
        if (uri.getScheme() == null) {
            text = "https://" + text;
        }

        text = URLUtil.guessUrl(text);

        if (URLUtil.isHttpsUrl(text) || URLUtil.isHttpUrl(text)) {
            mServerEdit.setText(text);
            return true;
        }

        return false;
    }

    @Override
    public Loader<CampaignReadResponse> onCreateLoader(int id, Bundle args) {
        return new CampaignReadTask(AuthenticatorActivity.this);
    }

    @Override
    public void onLoadFinished(Loader<CampaignReadResponse> loader, CampaignReadResponse data) {
        String urn = Campaign.getSingleCampaign(AuthenticatorActivity.this);
        if (urn == null) {
            Toast.makeText(AuthenticatorActivity.this, R.string.login_error_downloading_campaign, Toast.LENGTH_LONG)
                    .show();
        } else {
            createAccount();
        }
        dismissDialog(DIALOG_DOWNLOADING_CAMPAIGNS);
        finishLogin();
    }

    @Override
    public void onLoaderReset(Loader<CampaignReadResponse> loader) {
    }
}