Java tutorial
/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.actfm; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.json.JSONException; import org.json.JSONObject; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.text.InputType; import android.text.SpannableString; import android.text.Spanned; import android.text.TextPaint; import android.text.method.PasswordTransformationMethod; import android.text.style.ClickableSpan; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.facebook.android.AsyncFacebookRunner; import com.facebook.android.AsyncFacebookRunner.RequestListener; import com.facebook.android.AuthListener; import com.facebook.android.Facebook; import com.facebook.android.FacebookError; import com.facebook.android.LoginButton; import com.facebook.android.Util; import com.google.android.googlelogin.GoogleLoginServiceConstants; import com.google.android.googlelogin.GoogleLoginServiceHelper; import com.timsu.astrid.C2DMReceiver; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.ActFmInvoker; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.activity.Eula; import com.todoroo.astrid.gtasks.auth.ModernAuthManager; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MarketStrategy.AmazonMarketStrategy; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.SyncV2Service; import com.todoroo.astrid.service.TaskService; /** * This activity allows users to sign in or log in to Astrid.com * * @author Tim Su <tim@astrid.com> * */ public class ActFmLoginActivity extends FragmentActivity implements AuthListener { public static final String APP_ID = "183862944961271"; //$NON-NLS-1$ @Autowired protected ExceptionService exceptionService; @Autowired protected TaskService taskService; @Autowired protected ActFmPreferenceService actFmPreferenceService; @Autowired protected SyncV2Service syncService; private final ActFmInvoker actFmInvoker = new ActFmInvoker(); private Random rand; private Facebook facebook; private AsyncFacebookRunner facebookRunner; private TextView errors; public static final String SHOW_TOAST = "show_toast"; //$NON-NLS-1$ // --- ui initialization private static final int REQUEST_CODE_GOOGLE_ACCOUNTS = 1; private static final int REQUEST_CODE_GOOGLE = 2; static { AstridDependencyInjector.initialize(); } protected int getContentViewResource() { return R.layout.actfm_login_activity; } protected int getTitleResource() { return R.string.actfm_ALA_title; } public ActFmLoginActivity() { super(); DependencyInjectionService.getInstance().inject(this); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ContextManager.setContext(this); setContentView(getContentViewResource()); if (getTitleResource() != 0) setTitle(getTitleResource()); if (getSupportActionBar() != null) getSupportActionBar().hide(); rand = new Random(DateUtilities.now()); initializeUI(); getWindow().setFormat(PixelFormat.RGBA_8888); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DITHER); recordPageView(); setResult(RESULT_CANCELED); } /* (non-Javadoc) * @see android.app.Activity#onConfigurationChanged(android.content.res.Configuration) */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setContentView(getContentViewResource()); } @Override protected void onResume() { super.onResume(); StatisticsService.sessionStart(this); } @Override protected void onPause() { super.onPause(); StatisticsService.sessionPause(); } @Override protected void onStop() { StatisticsService.sessionStop(this); super.onStop(); } protected void recordPageView() { StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_SHOW); } protected void setupTermsOfService(TextView tos) { OnClickListener showTosListener = new OnClickListener() { @Override public void onClick(View v) { Eula.showEulaBasic(ActFmLoginActivity.this); } }; tos.setOnClickListener(showTosListener); String tosBase = getString(R.string.welcome_login_tos_base); String tosLink = getString(R.string.welcome_login_tos_link); SpannableString link = getLinkStringWithCustomInterval(tosBase, tosLink, tosBase.length() + 2, -1, showTosListener); tos.setText(link); } protected SpannableString getLinkStringWithCustomInterval(String base, String linkComponent, int start, int endOffset, final OnClickListener listener) { SpannableString link = new SpannableString(String.format("%s %s", //$NON-NLS-1$ base, linkComponent)); ClickableSpan linkSpan = new ClickableSpan() { @Override public void onClick(View widget) { listener.onClick(widget); } @Override public void updateDrawState(TextPaint ds) { ds.setUnderlineText(true); ds.setColor(Color.rgb(68, 68, 68)); } }; link.setSpan(linkSpan, start, link.length() + endOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return link; } @SuppressWarnings("nls") protected void initializeUI() { facebook = new Facebook(APP_ID); facebookRunner = new AsyncFacebookRunner(facebook); errors = (TextView) findViewById(R.id.error); LoginButton loginButton = (LoginButton) findViewById(R.id.fb_login); if (loginButton == null) return; loginButton.init(this, facebook, this, new String[] { "email", "offline_access", "publish_stream" }); View googleLogin = findViewById(R.id.gg_login); if (AmazonMarketStrategy.isKindleFire()) googleLogin.setVisibility(View.GONE); googleLogin.setOnClickListener(googleListener); Button pwLogin = (Button) findViewById(R.id.pw_login); pwLogin.setOnClickListener(signUpListener); setupTermsOfService((TextView) findViewById(R.id.tos)); } // --- event handler protected final OnClickListener googleListener = new OnClickListener() { @Override public void onClick(View arg0) { Intent intent = new Intent(ActFmLoginActivity.this, ActFmGoogleAuthActivity.class); startActivityForResult(intent, REQUEST_CODE_GOOGLE); StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_GL_START); } }; protected final OnClickListener signUpListener = new OnClickListener() { @Override public void onClick(View arg0) { final LinearLayout body = new LinearLayout(ActFmLoginActivity.this); body.setOrientation(LinearLayout.VERTICAL); body.setPadding(10, 0, 10, 0); final EditText firstNameField = addEditField(body, R.string.actfm_ALA_firstname_label); firstNameField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME | InputType.TYPE_TEXT_FLAG_CAP_WORDS); final EditText lastNameField = addEditField(body, R.string.actfm_ALA_lastname_label); lastNameField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME | InputType.TYPE_TEXT_FLAG_CAP_WORDS); final AtomicReference<AlertDialog> dialog = new AtomicReference<AlertDialog>(); final AtomicBoolean isNew = new AtomicBoolean(true); final Button toggleNew = new Button(ActFmLoginActivity.this); toggleNew.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { isNew.set(!isNew.get()); int nameIndex = body.indexOfChild(lastNameField); int visibility = isNew.get() ? View.VISIBLE : View.GONE; int passwordVisibility = isNew.get() ? View.GONE : View.VISIBLE; toggleNew.setText(isNew.get() ? R.string.actfm_ALA_pw_returning : R.string.actfm_ALA_pw_new); dialog.get().setTitle( isNew.get() ? R.string.actfm_ALA_signup_title : R.string.actfm_ALA_login_title); body.getChildAt(nameIndex - 3).setVisibility(visibility); body.getChildAt(nameIndex - 2).setVisibility(visibility); body.getChildAt(nameIndex - 1).setVisibility(visibility); body.getChildAt(nameIndex).setVisibility(visibility); EditText password = (EditText) body.getChildAt(nameIndex + 4); String passwordText = isNew.get() ? generateRandomPassword() : ""; //$NON-NLS-1$ password.setText(passwordText); body.getChildAt(nameIndex + 3).setVisibility(passwordVisibility); body.getChildAt(nameIndex + 4).setVisibility(passwordVisibility); } }); toggleNew.setText(R.string.actfm_ALA_pw_returning); body.addView(toggleNew, 0); final EditText email = addEditField(body, R.string.actfm_ALA_email_label); email.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); getCredentials(new OnGetCredentials() { @Override public void getCredentials(String[] accounts) { if (accounts != null && accounts.length > 0) email.setText(accounts[0]); } }); final EditText password = addEditField(body, R.string.actfm_ALA_password_label); password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); password.setTransformationMethod(new PasswordTransformationMethod()); password.setText(generateRandomPassword()); body.getChildAt(body.indexOfChild(password) - 1).setVisibility(View.GONE); password.setVisibility(View.GONE); ScrollView bodyScroll = new ScrollView(ActFmLoginActivity.this); bodyScroll.addView(body); dialog.set(new AlertDialog.Builder(ActFmLoginActivity.this).setView(bodyScroll) .setIcon(R.drawable.icon_32).setTitle(R.string.actfm_ALA_signup_title) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dlg, int which) { String firstName = isNew.get() ? firstNameField.getText().toString() : null; String lastName = isNew.get() ? lastNameField.getText().toString() : null; AndroidUtilities.hideSoftInputForViews(ActFmLoginActivity.this, firstNameField, lastNameField, email, password); authenticate(email.getText().toString(), firstName, lastName, ActFmInvoker.PROVIDER_PASSWORD, password.getText().toString()); if (isNew.get()) StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_PW); else StatisticsService.reportEvent(StatisticsConstants.ACTFM_SIGNUP_PW); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dlg, int which) { AndroidUtilities.hideSoftInputForViews(ActFmLoginActivity.this, firstNameField, lastNameField, email, password); } }).show()); dialog.get().setOwnerActivity(ActFmLoginActivity.this); } }; private String generateRandomPassword() { String acceptable = "abcdefghijklmnopqrstuvwxyz1234567890"; //$NON-NLS-1$ char[] chars = new char[8]; char last = 'a'; for (int i = 0; i < chars.length; i++) { char r = acceptable.charAt(rand.nextInt(acceptable.length())); while (!checkSimilar(last, r)) r = acceptable.charAt(rand.nextInt(acceptable.length())); last = r; chars[i] = r; } return new String(chars); } @SuppressWarnings("nls") private boolean checkSimilar(char last, char check) { String iSimilar = "ijl1!"; String oSimilar = "oO0"; String puncSimilar = ".,"; boolean match = (iSimilar.indexOf(last) > 0 && iSimilar.indexOf(check) > 0) || (oSimilar.indexOf(last) > 0 && oSimilar.indexOf(check) > 0) || (puncSimilar.indexOf(last) > 0 && puncSimilar.indexOf(check) > 0); if (match) return false; return true; } private EditText addEditField(LinearLayout body, int hint) { TextView label = new TextView(ActFmLoginActivity.this); label.setText(hint); body.addView(label); EditText field = new EditText(ActFmLoginActivity.this); field.setHint(hint); body.addView(field); return field; } // --- facebook handler public void onFBAuthSucceed() { createUserAccountFB(); } public void onFBAuthFail(String error) { DialogUtilities.okDialog(this, getString(R.string.actfm_ALA_title), android.R.drawable.ic_dialog_alert, error, null); } @Override public void onFBAuthCancel() { // do nothing } private ProgressDialog progressDialog; /** * Create user account via FB */ public void createUserAccountFB() { progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait)); facebookRunner.request("me", new SLARequestListener()); //$NON-NLS-1$ } private class SLARequestListener implements RequestListener { @Override public void onComplete(String response, Object state) { JSONObject json; try { json = Util.parseJson(response); String firstName = json.getString("first_name"); //$NON-NLS-1$ String lastName = json.getString("last_name"); //$NON-NLS-1$ String email = json.getString("email"); //$NON-NLS-1$ authenticate(email, firstName, lastName, ActFmInvoker.PROVIDER_FACEBOOK, facebook.getAccessToken()); StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_FB); } catch (FacebookError e) { handleError(e); } catch (JSONException e) { handleError(e); } } @Override public void onFacebookError(FacebookError e, Object state) { handleError(e); } @Override public void onFileNotFoundException(FileNotFoundException e, Object state) { handleError(e); } @Override public void onIOException(IOException e, Object state) { handleError(e); } @Override public void onMalformedURLException(MalformedURLException e, Object state) { handleError(e); } } // --- utilities @SuppressWarnings("nls") public void authenticate(final String email, final String firstName, final String lastName, final String provider, final String secret) { if (progressDialog == null) progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait)); new Thread() { @Override public void run() { try { final JSONObject result = actFmInvoker.authenticate(email, firstName, lastName, provider, secret); final String token = actFmInvoker.getToken(); if (result.optBoolean("new")) { // Report new user statistic StatisticsService.reportEvent(StatisticsConstants.ACTFM_NEW_USER, "provider", provider); } runOnUiThread(new Runnable() { public void run() { DialogUtilities.dismissDialog(ActFmLoginActivity.this, progressDialog); progressDialog = null; postAuthenticate(result, token); } }); } catch (IOException e) { handleError(e); } finally { runOnUiThread(new Runnable() { public void run() { if (progressDialog != null) { DialogUtilities.dismissDialog(ActFmLoginActivity.this, progressDialog); } } }); } } }.start(); } @SuppressWarnings("nls") protected void postAuthenticate(JSONObject result, String token) { actFmPreferenceService.setToken(token); Preferences.setLong(ActFmPreferenceService.PREF_USER_ID, result.optLong("id")); Preferences.setString(ActFmPreferenceService.PREF_NAME, result.optString("name")); Preferences.setString(ActFmPreferenceService.PREF_FIRST_NAME, result.optString("first_name")); Preferences.setString(ActFmPreferenceService.PREF_LAST_NAME, result.optString("last_name")); Preferences.setBoolean(ActFmPreferenceService.PREF_PREMIUM, result.optBoolean("premium")); Preferences.setString(ActFmPreferenceService.PREF_EMAIL, result.optString("email")); Preferences.setString(ActFmPreferenceService.PREF_PICTURE, result.optString("picture")); actFmPreferenceService.reloadThisUser(); setResult(RESULT_OK); finish(); try { C2DMReceiver.register(); } catch (Exception e) { // phone may not support c2dm exceptionService.reportError("error-c2dm-register", e); } } private void handleError(final Throwable e) { DialogUtilities.dismissDialog(this, progressDialog); exceptionService.reportError("astrid-sharing-login", e); //$NON-NLS-1$ runOnUiThread(new Runnable() { @Override public void run() { errors.setText(e.getMessage()); errors.setVisibility(View.VISIBLE); } }); } // --- google account manager @SuppressWarnings("nls") @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_CANCELED) return; if (requestCode == REQUEST_CODE_GOOGLE_ACCOUNTS) { String accounts[] = data.getExtras().getStringArray(GoogleLoginServiceConstants.ACCOUNTS_KEY); credentialsListener.getCredentials(accounts); } else if (requestCode == LoginButton.REQUEST_CODE_FACEBOOK) { if (data == null) return; String error = data.getStringExtra("error"); if (error == null) { error = data.getStringExtra("error_type"); } String token = data.getStringExtra("access_token"); if (error != null) { onFBAuthFail(error); } else if (token == null) { onFBAuthFail("Something went wrong! Please try again."); } else { facebook.setAccessToken(token); onFBAuthSucceed(); } errors.setVisibility(View.GONE); } else if (requestCode == REQUEST_CODE_GOOGLE) { if (data == null) return; String email = data.getStringExtra(ActFmGoogleAuthActivity.RESULT_EMAIL); String token = data.getStringExtra(ActFmGoogleAuthActivity.RESULT_TOKEN); authenticate(email, email, "", "google", token); } } public interface OnGetCredentials { public void getCredentials(String[] accounts); } private OnGetCredentials credentialsListener; public void getCredentials(OnGetCredentials onGetCredentials) { credentialsListener = onGetCredentials; if (Integer.parseInt(Build.VERSION.SDK) >= 7) credentialsListener.getCredentials(ModernAuthManager.getAccounts(this)); else GoogleLoginServiceHelper.getAccount(this, REQUEST_CODE_GOOGLE_ACCOUNTS, false); } }