mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.fxa.activities;
     7 import java.util.Calendar;
     8 import java.util.HashMap;
     9 import java.util.LinkedList;
    10 import java.util.Map;
    11 import java.util.concurrent.Executor;
    12 import java.util.concurrent.Executors;
    14 import org.mozilla.gecko.R;
    15 import org.mozilla.gecko.background.common.log.Logger;
    16 import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
    17 import org.mozilla.gecko.background.fxa.FxAccountClient;
    18 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
    19 import org.mozilla.gecko.background.fxa.FxAccountClient20;
    20 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
    21 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
    22 import org.mozilla.gecko.background.fxa.PasswordStretcher;
    23 import org.mozilla.gecko.fxa.FxAccountConstants;
    24 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
    26 import android.app.AlertDialog;
    27 import android.app.Dialog;
    28 import android.content.DialogInterface;
    29 import android.content.Intent;
    30 import android.os.Bundle;
    31 import android.os.SystemClock;
    32 import android.text.Spannable;
    33 import android.text.Spanned;
    34 import android.text.method.LinkMovementMethod;
    35 import android.text.style.ClickableSpan;
    36 import android.view.View;
    37 import android.view.View.OnClickListener;
    38 import android.widget.AutoCompleteTextView;
    39 import android.widget.Button;
    40 import android.widget.CheckBox;
    41 import android.widget.EditText;
    42 import android.widget.ListView;
    43 import android.widget.ProgressBar;
    44 import android.widget.TextView;
    46 /**
    47  * Activity which displays create account screen to the user.
    48  */
    49 public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivity {
    50   protected static final String LOG_TAG = FxAccountCreateAccountActivity.class.getSimpleName();
    52   private static final int CHILD_REQUEST_CODE = 2;
    54   protected String[] yearItems;
    55   protected EditText yearEdit;
    56   protected CheckBox chooseCheckBox;
    58   protected Map<String, Boolean> selectedEngines;
    60   /**
    61    * {@inheritDoc}
    62    */
    63   @Override
    64   public void onCreate(Bundle icicle) {
    65     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
    67     super.onCreate(icicle);
    68     setContentView(R.layout.fxaccount_create_account);
    70     emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
    71     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
    72     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
    73     yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit");
    74     remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
    75     button = (Button) ensureFindViewById(null, R.id.button, "create account button");
    76     progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
    77     chooseCheckBox = (CheckBox) ensureFindViewById(null, R.id.choose_what_to_sync_checkbox, "choose what to sync check box");
    78     selectedEngines = new HashMap<String, Boolean>();
    80     createCreateAccountButton();
    81     createYearEdit();
    82     addListeners();
    83     updateButtonState();
    84     createShowPasswordButton();
    85     linkifyPolicy();
    86     createChooseCheckBox();
    88     View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link");
    89     signInInsteadLink.setOnClickListener(new OnClickListener() {
    90       @Override
    91       public void onClick(View v) {
    92         final String email = emailEdit.getText().toString();
    93         final String password = passwordEdit.getText().toString();
    94         doSigninInstead(email, password);
    95       }
    96     });
    98     // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
    99     if (getIntent() != null && getIntent().getExtras() != null) {
   100       Bundle bundle = getIntent().getExtras();
   101       emailEdit.setText(bundle.getString("email"));
   102       passwordEdit.setText(bundle.getString("password"));
   103     }
   104   }
   106   protected void doSigninInstead(final String email, final String password) {
   107     Intent intent = new Intent(this, FxAccountSignInActivity.class);
   108     if (email != null) {
   109       intent.putExtra("email", email);
   110     }
   111     if (password != null) {
   112       intent.putExtra("password", password);
   113     }
   114     // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
   115     // the soft keyboard not being shown for the started activity. Why, Android, why?
   116     intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
   117     startActivityForResult(intent, CHILD_REQUEST_CODE);
   118   }
   120   @Override
   121   protected void showClientRemoteException(final FxAccountClientRemoteException e) {
   122     if (!e.isAccountAlreadyExists()) {
   123       super.showClientRemoteException(e);
   124       return;
   125     }
   127     // This horrible bit of special-casing is because we want this error message to
   128     // contain a clickable, extra chunk of text, but we don't want to pollute
   129     // the exception class with Android specifics.
   130     final String clickablePart = getString(R.string.fxaccount_sign_in_button_label);
   131     final String message = getString(e.getErrorMessageStringResource(), clickablePart);
   132     final int clickableStart = message.lastIndexOf(clickablePart);
   133     final int clickableEnd = clickableStart + clickablePart.length();
   135     final Spannable span = Spannable.Factory.getInstance().newSpannable(message);
   136     span.setSpan(new ClickableSpan() {
   137       @Override
   138       public void onClick(View widget) {
   139         // Pass through the email address that already existed.
   140         String email = e.body.getString("email");
   141         if (email == null) {
   142             email = emailEdit.getText().toString();
   143         }
   144         final String password = passwordEdit.getText().toString();
   145         doSigninInstead(email, password);
   146       }
   147     }, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   148     remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
   149     remoteErrorTextView.setText(span);
   150   }
   152   /**
   153    * We might have switched to the SignIn activity; if that activity
   154    * succeeds, feed its result back to the authenticator.
   155    */
   156   @Override
   157   public void onActivityResult(int requestCode, int resultCode, Intent data) {
   158     Logger.debug(LOG_TAG, "onActivityResult: " + requestCode);
   159     if (requestCode != CHILD_REQUEST_CODE || resultCode != RESULT_OK) {
   160       super.onActivityResult(requestCode, resultCode, data);
   161       return;
   162     }
   163     this.setResult(resultCode, data);
   164     this.finish();
   165   }
   167   /**
   168    * Return years to display in picker.
   169    *
   170    * @return 1990 or earlier, 1991, 1992, up to five years before current year.
   171    *         (So, if it is currently 2014, up to 2009.)
   172    */
   173   protected String[] getYearItems() {
   174     int year = Calendar.getInstance().get(Calendar.YEAR);
   175     LinkedList<String> years = new LinkedList<String>();
   176     years.add(getResources().getString(R.string.fxaccount_create_account_1990_or_earlier));
   177     for (int i = 1991; i <= year - 5; i++) {
   178       years.add(Integer.toString(i));
   179     }
   180     return years.toArray(new String[0]);
   181   }
   183   protected void createYearEdit() {
   184     yearItems = getYearItems();
   186     yearEdit.setOnClickListener(new OnClickListener() {
   187       @Override
   188       public void onClick(View v) {
   189         android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() {
   190           @Override
   191           public void onClick(DialogInterface dialog, int which) {
   192             yearEdit.setText(yearItems[which]);
   193             updateButtonState();
   194           }
   195         };
   196         final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
   197         .setTitle(R.string.fxaccount_create_account_year_of_birth)
   198         .setItems(yearItems, listener)
   199         .setIcon(R.drawable.icon)
   200         .create();
   202         dialog.show();
   203       }
   204     });
   205   }
   207   public void createAccount(String email, String password, Map<String, Boolean> engines) {
   208     String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
   209     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
   210     // This delegate creates a new Android account on success, opens the
   211     // appropriate "success!" activity, and finishes this activity.
   212     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI, engines) {
   213       @Override
   214       public void handleError(Exception e) {
   215         showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
   216       }
   218       @Override
   219       public void handleFailure(FxAccountClientRemoteException e) {
   220         showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
   221       }
   222     };
   224     Executor executor = Executors.newSingleThreadExecutor();
   225     FxAccountClient client = new FxAccountClient20(serverURI, executor);
   226     try {
   227       hideRemoteError();
   228       new FxAccountCreateAccountTask(this, this, email, passwordStretcher, client, delegate).execute();
   229     } catch (Exception e) {
   230       showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
   231     }
   232   }
   234   @Override
   235   protected boolean shouldButtonBeEnabled() {
   236     return super.shouldButtonBeEnabled() &&
   237         (yearEdit.length() > 0);
   238   }
   240   protected void createCreateAccountButton() {
   241     button.setOnClickListener(new OnClickListener() {
   242       @Override
   243       public void onClick(View v) {
   244         if (!updateButtonState()) {
   245           return;
   246         }
   247         final String email = emailEdit.getText().toString();
   248         final String password = passwordEdit.getText().toString();
   249         // Only include selected engines if the user currently has the option checked.
   250         final Map<String, Boolean> engines = chooseCheckBox.isChecked()
   251             ? selectedEngines
   252             : null;
   253         if (FxAccountAgeLockoutHelper.passesAgeCheck(yearEdit.getText().toString(), yearItems)) {
   254           FxAccountConstants.pii(LOG_TAG, "Passed age check.");
   255           createAccount(email, password, engines);
   256         } else {
   257           FxAccountConstants.pii(LOG_TAG, "Failed age check!");
   258           FxAccountAgeLockoutHelper.lockOut(SystemClock.elapsedRealtime());
   259           setResult(RESULT_CANCELED);
   260           redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
   261         }
   262       }
   263     });
   264   }
   266   /**
   267    * The "Choose what to sync" checkbox pops up a multi-choice dialog when it is
   268    * unchecked. It toggles to unchecked from checked.
   269    */
   270   protected void createChooseCheckBox() {
   271     final int INDEX_BOOKMARKS = 0;
   272     final int INDEX_HISTORY = 1;
   273     final int INDEX_TABS = 2;
   274     final int INDEX_PASSWORDS = 3;
   275     final int NUMBER_OF_ENGINES = 4;
   277     final String items[] = new String[NUMBER_OF_ENGINES];
   278     final boolean checkedItems[] = new boolean[NUMBER_OF_ENGINES];
   279     items[INDEX_BOOKMARKS] = getResources().getString(R.string.fxaccount_status_bookmarks);
   280     items[INDEX_HISTORY] = getResources().getString(R.string.fxaccount_status_history);
   281     items[INDEX_TABS] = getResources().getString(R.string.fxaccount_status_tabs);
   282     items[INDEX_PASSWORDS] = getResources().getString(R.string.fxaccount_status_passwords);
   283     // Default to everything checked.
   284     for (int i = 0; i < NUMBER_OF_ENGINES; i++) {
   285       checkedItems[i] = true;
   286     }
   288     final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
   289       @Override
   290       public void onClick(DialogInterface dialog, int which) {
   291         if (which != DialogInterface.BUTTON_POSITIVE) {
   292           Logger.debug(LOG_TAG, "onClick: not button positive, unchecking.");
   293           chooseCheckBox.setChecked(false);
   294           return;
   295         }
   296         // We only check the box on success.
   297         Logger.debug(LOG_TAG, "onClick: button positive, checking.");
   298         chooseCheckBox.setChecked(true);
   299         // And then remember for future use.
   300         ListView selectionsList = ((AlertDialog) dialog).getListView();
   301         for (int i = 0; i < NUMBER_OF_ENGINES; i++) {
   302           checkedItems[i] = selectionsList.isItemChecked(i);
   303         }
   304         selectedEngines.put("bookmarks", checkedItems[INDEX_BOOKMARKS]);
   305         selectedEngines.put("history", checkedItems[INDEX_HISTORY]);
   306         selectedEngines.put("tabs", checkedItems[INDEX_TABS]);
   307         selectedEngines.put("passwords", checkedItems[INDEX_PASSWORDS]);
   308         FxAccountConstants.pii(LOG_TAG, "Updating selectedEngines: " + selectedEngines.toString());
   309       }
   310     };
   312     final DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener = new DialogInterface.OnMultiChoiceClickListener() {
   313       @Override
   314       public void onClick(DialogInterface dialog, int which, boolean isChecked) {
   315         // Display multi-selection clicks in UI.
   316         ListView selectionsList = ((AlertDialog) dialog).getListView();
   317         selectionsList.setItemChecked(which, isChecked);
   318       }
   319     };
   321     final AlertDialog dialog = new AlertDialog.Builder(this)
   322         .setTitle(R.string.fxaccount_create_account_choose_what_to_sync)
   323         .setIcon(R.drawable.icon)
   324         .setMultiChoiceItems(items, checkedItems, multiChoiceClickListener)
   325         .setPositiveButton(android.R.string.ok, clickListener)
   326         .setNegativeButton(android.R.string.cancel, clickListener)
   327         .create();
   329     dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
   330       @Override
   331       public void onCancel(DialogInterface dialog) {
   332         Logger.debug(LOG_TAG, "onCancel: unchecking.");
   333         chooseCheckBox.setChecked(false);
   334       }
   335     });
   337     chooseCheckBox.setOnClickListener(new OnClickListener() {
   338       @Override
   339       public void onClick(View v) {
   340         // There appears to be no way to stop Android interpreting the click
   341         // first. So, if the user clicked on an unchecked box, it's checked by
   342         // the time we get here.
   343         if (!chooseCheckBox.isChecked()) {
   344           Logger.debug(LOG_TAG, "onClick: was checked, not showing dialog.");
   345           return;
   346         }
   347         Logger.debug(LOG_TAG, "onClick: was unchecked, showing dialog.");
   348         dialog.show();
   349       }
   350     });
   351   }
   352 }

mercurial