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