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.

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

mercurial