Home | History | Annotate | Download | only in authenticator
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.example.android.samplesync.authenticator;
     18 
     19 import com.example.android.samplesync.Constants;
     20 import com.example.android.samplesync.R;
     21 import com.example.android.samplesync.client.NetworkUtilities;
     22 
     23 import android.accounts.Account;
     24 import android.accounts.AccountAuthenticatorActivity;
     25 import android.accounts.AccountManager;
     26 import android.app.Dialog;
     27 import android.app.ProgressDialog;
     28 import android.content.ContentResolver;
     29 import android.content.DialogInterface;
     30 import android.content.Intent;
     31 import android.os.AsyncTask;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.provider.ContactsContract;
     35 import android.text.TextUtils;
     36 import android.util.Log;
     37 import android.view.View;
     38 import android.view.Window;
     39 import android.widget.EditText;
     40 import android.widget.TextView;
     41 
     42 /**
     43  * Activity which displays login screen to the user.
     44  */
     45 public class AuthenticatorActivity extends AccountAuthenticatorActivity {
     46     /** The Intent flag to confirm credentials. */
     47     public static final String PARAM_CONFIRM_CREDENTIALS = "confirmCredentials";
     48 
     49     /** The Intent extra to store password. */
     50     public static final String PARAM_PASSWORD = "password";
     51 
     52     /** The Intent extra to store username. */
     53     public static final String PARAM_USERNAME = "username";
     54 
     55     /** The Intent extra to store username. */
     56     public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
     57 
     58     /** The tag used to log to adb console. */
     59     private static final String TAG = "AuthenticatorActivity";
     60     private AccountManager mAccountManager;
     61 
     62     /** Keep track of the login task so can cancel it if requested */
     63     private UserLoginTask mAuthTask = null;
     64 
     65     /** Keep track of the progress dialog so we can dismiss it */
     66     private ProgressDialog mProgressDialog = null;
     67 
     68     /**
     69      * If set we are just checking that the user knows their credentials; this
     70      * doesn't cause the user's password or authToken to be changed on the
     71      * device.
     72      */
     73     private Boolean mConfirmCredentials = false;
     74 
     75     /** for posting authentication attempts back to UI thread */
     76     private final Handler mHandler = new Handler();
     77 
     78     private TextView mMessage;
     79 
     80     private String mPassword;
     81 
     82     private EditText mPasswordEdit;
     83 
     84     /** Was the original caller asking for an entirely new account? */
     85     protected boolean mRequestNewAccount = false;
     86 
     87     private String mUsername;
     88 
     89     private EditText mUsernameEdit;
     90 
     91     /**
     92      * {@inheritDoc}
     93      */
     94     @Override
     95     public void onCreate(Bundle icicle) {
     96 
     97         Log.i(TAG, "onCreate(" + icicle + ")");
     98         super.onCreate(icicle);
     99         mAccountManager = AccountManager.get(this);
    100         Log.i(TAG, "loading data from Intent");
    101         final Intent intent = getIntent();
    102         mUsername = intent.getStringExtra(PARAM_USERNAME);
    103         mRequestNewAccount = mUsername == null;
    104         mConfirmCredentials = intent.getBooleanExtra(PARAM_CONFIRM_CREDENTIALS, false);
    105         Log.i(TAG, "    request new: " + mRequestNewAccount);
    106         requestWindowFeature(Window.FEATURE_LEFT_ICON);
    107         setContentView(R.layout.login_activity);
    108         getWindow().setFeatureDrawableResource(
    109                 Window.FEATURE_LEFT_ICON, android.R.drawable.ic_dialog_alert);
    110         mMessage = (TextView) findViewById(R.id.message);
    111         mUsernameEdit = (EditText) findViewById(R.id.username_edit);
    112         mPasswordEdit = (EditText) findViewById(R.id.password_edit);
    113         if (!TextUtils.isEmpty(mUsername)) mUsernameEdit.setText(mUsername);
    114         mMessage.setText(getMessage());
    115     }
    116 
    117     /*
    118      * {@inheritDoc}
    119      */
    120     @Override
    121     protected Dialog onCreateDialog(int id, Bundle args) {
    122         final ProgressDialog dialog = new ProgressDialog(this);
    123         dialog.setMessage(getText(R.string.ui_activity_authenticating));
    124         dialog.setIndeterminate(true);
    125         dialog.setCancelable(true);
    126         dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    127             public void onCancel(DialogInterface dialog) {
    128                 Log.i(TAG, "user cancelling authentication");
    129                 if (mAuthTask != null) {
    130                     mAuthTask.cancel(true);
    131                 }
    132             }
    133         });
    134         // We save off the progress dialog in a field so that we can dismiss
    135         // it later. We can't just call dismissDialog(0) because the system
    136         // can lose track of our dialog if there's an orientation change.
    137         mProgressDialog = dialog;
    138         return dialog;
    139     }
    140 
    141     /**
    142      * Handles onClick event on the Submit button. Sends username/password to
    143      * the server for authentication. The button is configured to call
    144      * handleLogin() in the layout XML.
    145      *
    146      * @param view The Submit button for which this method is invoked
    147      */
    148     public void handleLogin(View view) {
    149         if (mRequestNewAccount) {
    150             mUsername = mUsernameEdit.getText().toString();
    151         }
    152         mPassword = mPasswordEdit.getText().toString();
    153         if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {
    154             mMessage.setText(getMessage());
    155         } else {
    156             // Show a progress dialog, and kick off a background task to perform
    157             // the user login attempt.
    158             showProgress();
    159             mAuthTask = new UserLoginTask();
    160             mAuthTask.execute();
    161         }
    162     }
    163 
    164     /**
    165      * Called when response is received from the server for confirm credentials
    166      * request. See onAuthenticationResult(). Sets the
    167      * AccountAuthenticatorResult which is sent back to the caller.
    168      *
    169      * @param result the confirmCredentials result.
    170      */
    171     private void finishConfirmCredentials(boolean result) {
    172         Log.i(TAG, "finishConfirmCredentials()");
    173         final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
    174         mAccountManager.setPassword(account, mPassword);
    175         final Intent intent = new Intent();
    176         intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
    177         setAccountAuthenticatorResult(intent.getExtras());
    178         setResult(RESULT_OK, intent);
    179         finish();
    180     }
    181 
    182     /**
    183      * Called when response is received from the server for authentication
    184      * request. See onAuthenticationResult(). Sets the
    185      * AccountAuthenticatorResult which is sent back to the caller. We store the
    186      * authToken that's returned from the server as the 'password' for this
    187      * account - so we're never storing the user's actual password locally.
    188      *
    189      * @param result the confirmCredentials result.
    190      */
    191     private void finishLogin(String authToken) {
    192 
    193         Log.i(TAG, "finishLogin()");
    194         final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
    195         if (mRequestNewAccount) {
    196             mAccountManager.addAccountExplicitly(account, mPassword, null);
    197             // Set contacts sync for this account.
    198             ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
    199         } else {
    200             mAccountManager.setPassword(account, mPassword);
    201         }
    202         final Intent intent = new Intent();
    203         intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
    204         intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
    205         setAccountAuthenticatorResult(intent.getExtras());
    206         setResult(RESULT_OK, intent);
    207         finish();
    208     }
    209 
    210     /**
    211      * Called when the authentication process completes (see attemptLogin()).
    212      *
    213      * @param authToken the authentication token returned by the server, or NULL if
    214      *            authentication failed.
    215      */
    216     public void onAuthenticationResult(String authToken) {
    217 
    218         boolean success = ((authToken != null) && (authToken.length() > 0));
    219         Log.i(TAG, "onAuthenticationResult(" + success + ")");
    220 
    221         // Our task is complete, so clear it out
    222         mAuthTask = null;
    223 
    224         // Hide the progress dialog
    225         hideProgress();
    226 
    227         if (success) {
    228             if (!mConfirmCredentials) {
    229                 finishLogin(authToken);
    230             } else {
    231                 finishConfirmCredentials(success);
    232             }
    233         } else {
    234             Log.e(TAG, "onAuthenticationResult: failed to authenticate");
    235             if (mRequestNewAccount) {
    236                 // "Please enter a valid username/password.
    237                 mMessage.setText(getText(R.string.login_activity_loginfail_text_both));
    238             } else {
    239                 // "Please enter a valid password." (Used when the
    240                 // account is already in the database but the password
    241                 // doesn't work.)
    242                 mMessage.setText(getText(R.string.login_activity_loginfail_text_pwonly));
    243             }
    244         }
    245     }
    246 
    247     public void onAuthenticationCancel() {
    248         Log.i(TAG, "onAuthenticationCancel()");
    249 
    250         // Our task is complete, so clear it out
    251         mAuthTask = null;
    252 
    253         // Hide the progress dialog
    254         hideProgress();
    255     }
    256 
    257     /**
    258      * Returns the message to be displayed at the top of the login dialog box.
    259      */
    260     private CharSequence getMessage() {
    261         getString(R.string.label);
    262         if (TextUtils.isEmpty(mUsername)) {
    263             // If no username, then we ask the user to log in using an
    264             // appropriate service.
    265             final CharSequence msg = getText(R.string.login_activity_newaccount_text);
    266             return msg;
    267         }
    268         if (TextUtils.isEmpty(mPassword)) {
    269             // We have an account but no password
    270             return getText(R.string.login_activity_loginfail_text_pwmissing);
    271         }
    272         return null;
    273     }
    274 
    275     /**
    276      * Shows the progress UI for a lengthy operation.
    277      */
    278     private void showProgress() {
    279         showDialog(0);
    280     }
    281 
    282     /**
    283      * Hides the progress UI for a lengthy operation.
    284      */
    285     private void hideProgress() {
    286         if (mProgressDialog != null) {
    287             mProgressDialog.dismiss();
    288             mProgressDialog = null;
    289         }
    290     }
    291 
    292     /**
    293      * Represents an asynchronous task used to authenticate a user against the
    294      * SampleSync Service
    295      */
    296     public class UserLoginTask extends AsyncTask<Void, Void, String> {
    297 
    298         @Override
    299         protected String doInBackground(Void... params) {
    300             // We do the actual work of authenticating the user
    301             // in the NetworkUtilities class.
    302             try {
    303                 return NetworkUtilities.authenticate(mUsername, mPassword);
    304             } catch (Exception ex) {
    305                 Log.e(TAG, "UserLoginTask.doInBackground: failed to authenticate");
    306                 Log.i(TAG, ex.toString());
    307                 return null;
    308             }
    309         }
    310 
    311         @Override
    312         protected void onPostExecute(final String authToken) {
    313             // On a successful authentication, call back into the Activity to
    314             // communicate the authToken (or null for an error).
    315             onAuthenticationResult(authToken);
    316         }
    317 
    318         @Override
    319         protected void onCancelled() {
    320             // If the action was canceled (by the user clicking the cancel
    321             // button in the progress dialog), then call back into the
    322             // activity to let it know.
    323             onAuthenticationCancel();
    324         }
    325     }
    326 }
    327