/* * Copyright 2012 GitHub Inc. * * 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 com.github.mobile.gauges.authenticator; import static android.R.layout.simple_dropdown_item_1line; import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; import static android.accounts.AccountManager.KEY_AUTHTOKEN; import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT; import static com.github.kevinsawicki.http.HttpRequest.post; import static com.github.mobile.gauges.authenticator.AuthConstants.GAUGES_ACCOUNT_TYPE; import static com.github.mobile.gauges.core.GaugesConstants.URL_AUTH; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.TextView; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.github.kevinsawicki.http.HttpRequest; import com.github.kevinsawicki.wishlist.EditTextUtils; import com.github.kevinsawicki.wishlist.EditTextUtils.BooleanRunnable; import com.github.kevinsawicki.wishlist.Toaster; import com.github.mobile.gauges.R.id; import com.github.mobile.gauges.R.layout; import com.github.mobile.gauges.R.menu; import com.github.mobile.gauges.R.string; import com.github.mobile.gauges.ui.TextWatcherAdapter; import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockAccountAuthenticatorActivity; import java.util.ArrayList; import java.util.List; import roboguice.inject.InjectView; import roboguice.util.RoboAsyncTask; /** * Activity to authenticate the user against gaug.es */ public class GaugesAuthenticatorActivity extends RoboSherlockAccountAuthenticatorActivity { /** * PARAM_CONFIRMCREDENTIALS */ public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials"; /** * PARAM_PASSWORD */ public static final String PARAM_PASSWORD = "password"; /** * PARAM_USERNAME */ public static final String PARAM_USERNAME = "username"; /** * PARAM_AUTHTOKEN_TYPE */ public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType"; private static final String TAG = "GaugesAuthActivity"; private AccountManager accountManager; @InjectView(id.et_email) private AutoCompleteTextView emailText; @InjectView(id.et_password) private EditText passwordText; private MenuItem signInItem; private TextWatcher watcher = validationTextWatcher(); private RoboAsyncTask<Boolean> authenticationTask; private String authToken; private String authTokenType; /** * 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 confirmCredentials = false; private String email; private String password; /** * Was the original caller asking for an entirely new account? */ protected boolean requestNewAccount = false; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); accountManager = AccountManager.get(this); final Intent intent = getIntent(); email = intent.getStringExtra(PARAM_USERNAME); authTokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE); requestNewAccount = email == null; confirmCredentials = intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS, false); setContentView(layout.login_activity); emailText.setAdapter(new ArrayAdapter<String>(this, simple_dropdown_item_1line, userEmailAccounts())); EditTextUtils.onDone(passwordText, new BooleanRunnable() { @Override public boolean run() { if (signInItem != null && signInItem.isEnabled()) { handleLogin(); return true; } return false; } }); emailText.addTextChangedListener(watcher); passwordText.addTextChangedListener(watcher); TextView signupText = (TextView) findViewById(id.tv_signup); signupText.setMovementMethod(LinkMovementMethod.getInstance()); signupText.setText(Html.fromHtml(getString(string.signup_link))); } private List<String> userEmailAccounts() { Account[] accounts = accountManager.getAccountsByType("com.google"); List<String> emailAddresses = new ArrayList<String>(accounts.length); for (Account account : accounts) emailAddresses.add(account.name); return emailAddresses; } private TextWatcher validationTextWatcher() { return new TextWatcherAdapter() { public void afterTextChanged(Editable gitDirEditText) { updateUIWithValidation(); } }; } @Override protected void onResume() { super.onResume(); updateUIWithValidation(); } @Override public boolean onCreateOptionsMenu(Menu optionsMenu) { getSupportMenuInflater().inflate(menu.login, optionsMenu); signInItem = optionsMenu.findItem(id.m_login); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { updateUIWithValidation(); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case id.m_login: handleLogin(); return true; default: return super.onOptionsItemSelected(item); } } private void updateUIWithValidation() { boolean populated = populated(emailText) && populated(passwordText); if (signInItem != null) signInItem.setEnabled(populated); } private boolean populated(EditText editText) { return editText != null && editText.length() > 0; } @Override protected Dialog onCreateDialog(int id) { final ProgressDialog dialog = new ProgressDialog(this); dialog.setMessage(getText(string.message_signing_in)); dialog.setIndeterminate(true); dialog.setCancelable(true); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { if (authenticationTask != null) authenticationTask.cancel(true); } }); return dialog; } /** * Handles onClick event on the Submit button. Sends username/password to * the server for authentication. */ public void handleLogin() { if (authenticationTask != null) return; if (requestNewAccount) email = emailText.getText().toString(); password = passwordText.getText().toString(); showProgress(); authenticationTask = new RoboAsyncTask<Boolean>(this) { public Boolean call() throws Exception { HttpRequest request = post(URL_AUTH).form("email", email).form( "password", password); Log.d(TAG, "response=" + request.code()); return request.ok(); } @Override protected void onException(Exception e) throws RuntimeException { Throwable cause = e.getCause() != null ? e.getCause() : e; String message; // A 401 is returned as an IOException with this message if ("Received authentication challenge is null".equals(cause .getMessage())) message = getString(string.message_bad_credentials); else if ("No authentication challenges found".equals(cause .getMessage())) message = getString(string.message_bad_credentials); else message = cause.getMessage(); Toaster.showLong(GaugesAuthenticatorActivity.this, message); } @Override public void onSuccess(Boolean authSuccess) { onAuthenticationResult(authSuccess); } @Override protected void onFinally() throws RuntimeException { hideProgress(); authenticationTask = null; } }; authenticationTask.execute(); } /** * 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 result */ protected void finishConfirmCredentials(boolean result) { final Account account = new Account(email, GAUGES_ACCOUNT_TYPE); accountManager.setPassword(account, password); final Intent intent = new Intent(); intent.putExtra(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. */ protected void finishLogin() { final Account account = new Account(email, GAUGES_ACCOUNT_TYPE); if (requestNewAccount) accountManager.addAccountExplicitly(account, password, null); else accountManager.setPassword(account, password); final Intent intent = new Intent(); authToken = password; intent.putExtra(KEY_ACCOUNT_NAME, email); intent.putExtra(KEY_ACCOUNT_TYPE, GAUGES_ACCOUNT_TYPE); if (authTokenType != null && authTokenType.equals(AuthConstants.AUTHTOKEN_TYPE)) intent.putExtra(KEY_AUTHTOKEN, authToken); setAccountAuthenticatorResult(intent.getExtras()); setResult(RESULT_OK, intent); finish(); } /** * Hide progress dialog */ @SuppressWarnings("deprecation") protected void hideProgress() { dismissDialog(0); } /** * Show progress dialog */ @SuppressWarnings("deprecation") protected void showProgress() { showDialog(0); } /** * Called when the authentication process completes (see attemptLogin()). * * @param result */ public void onAuthenticationResult(boolean result) { if (result) if (!confirmCredentials) finishLogin(); else finishConfirmCredentials(true); else { Log.e(TAG, "onAuthenticationResult: failed to authenticate"); if (requestNewAccount) Toaster.showLong(GaugesAuthenticatorActivity.this, string.message_auth_failed_new_account); else Toaster.showLong(GaugesAuthenticatorActivity.this, string.message_auth_failed); } } }