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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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.io.IOException;
     8 import java.util.Arrays;
     9 import java.util.HashSet;
    10 import java.util.Map;
    11 import java.util.Set;
    13 import org.mozilla.gecko.R;
    14 import org.mozilla.gecko.background.common.log.Logger;
    15 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
    16 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
    17 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
    18 import org.mozilla.gecko.background.fxa.FxAccountUtils;
    19 import org.mozilla.gecko.background.fxa.PasswordStretcher;
    20 import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
    21 import org.mozilla.gecko.fxa.FxAccountConstants;
    22 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
    23 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
    24 import org.mozilla.gecko.fxa.login.Engaged;
    25 import org.mozilla.gecko.fxa.login.State;
    26 import org.mozilla.gecko.sync.SyncConfiguration;
    27 import org.mozilla.gecko.sync.setup.Constants;
    28 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
    30 import android.accounts.Account;
    31 import android.accounts.AccountManager;
    32 import android.content.Context;
    33 import android.content.Intent;
    34 import android.os.AsyncTask;
    35 import android.text.Editable;
    36 import android.text.TextWatcher;
    37 import android.text.method.PasswordTransformationMethod;
    38 import android.text.method.SingleLineTransformationMethod;
    39 import android.util.Patterns;
    40 import android.view.KeyEvent;
    41 import android.view.View;
    42 import android.view.View.OnClickListener;
    43 import android.view.View.OnFocusChangeListener;
    44 import android.widget.ArrayAdapter;
    45 import android.widget.AutoCompleteTextView;
    46 import android.widget.Button;
    47 import android.widget.EditText;
    48 import android.widget.ProgressBar;
    49 import android.widget.TextView;
    50 import android.widget.TextView.OnEditorActionListener;
    52 abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
    53   public FxAccountAbstractSetupActivity() {
    54     super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
    55   }
    57   protected FxAccountAbstractSetupActivity(int resume) {
    58     super(resume);
    59   }
    61   private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
    63   protected int minimumPasswordLength = 8;
    65   protected AutoCompleteTextView emailEdit;
    66   protected EditText passwordEdit;
    67   protected Button showPasswordButton;
    68   protected TextView remoteErrorTextView;
    69   protected Button button;
    70   protected ProgressBar progressBar;
    72   protected void createShowPasswordButton() {
    73     showPasswordButton.setOnClickListener(new OnClickListener() {
    74       @SuppressWarnings("deprecation")
    75       @Override
    76       public void onClick(View v) {
    77         boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
    79         // Changing input type loses position in edit text; let's try to maintain it.
    80         int start = passwordEdit.getSelectionStart();
    81         int stop = passwordEdit.getSelectionEnd();
    83         if (isShown) {
    84           passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
    85           showPasswordButton.setText(R.string.fxaccount_password_show);
    86           showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
    87           showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
    88         } else {
    89           passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
    90           showPasswordButton.setText(R.string.fxaccount_password_hide);
    91           showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
    92           showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
    93         }
    94         passwordEdit.setSelection(start, stop);
    95       }
    96     });
    97   }
    99   protected void linkifyPolicy() {
   100     TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
   101     final String linkTerms = getString(R.string.fxaccount_link_tos);
   102     final String linkPrivacy = getString(R.string.fxaccount_link_pn);
   103     final String linkedTOS = "<a href=\"" + linkTerms + "\">" + getString(R.string.fxaccount_policy_linktos) + "</a>";
   104     final String linkedPN = "<a href=\"" + linkPrivacy + "\">" + getString(R.string.fxaccount_policy_linkprivacy) + "</a>";
   105     policyView.setText(getString(R.string.fxaccount_create_account_policy_text, linkedTOS, linkedPN));
   106     final boolean underlineLinks = true;
   107     ActivityUtils.linkifyTextView(policyView, underlineLinks);
   108   }
   110   protected void hideRemoteError() {
   111     remoteErrorTextView.setVisibility(View.INVISIBLE);
   112   }
   114   protected void showRemoteError(Exception e, int defaultResourceId) {
   115     if (e instanceof IOException) {
   116       remoteErrorTextView.setText(R.string.fxaccount_remote_error_COULD_NOT_CONNECT);
   117     } else if (e instanceof FxAccountClientRemoteException) {
   118       showClientRemoteException((FxAccountClientRemoteException) e);
   119     } else {
   120       remoteErrorTextView.setText(defaultResourceId);
   121     }
   122     Logger.warn(LOG_TAG, "Got exception; showing error message: " + remoteErrorTextView.getText().toString(), e);
   123     remoteErrorTextView.setVisibility(View.VISIBLE);
   124   }
   126   protected void showClientRemoteException(final FxAccountClientRemoteException e) {
   127     remoteErrorTextView.setText(e.getErrorMessageStringResource());
   128   }
   130   protected void addListeners() {
   131     TextChangedListener textChangedListener = new TextChangedListener();
   132     EditorActionListener editorActionListener = new EditorActionListener();
   133     FocusChangeListener focusChangeListener = new FocusChangeListener();
   135     emailEdit.addTextChangedListener(textChangedListener);
   136     emailEdit.setOnEditorActionListener(editorActionListener);
   137     emailEdit.setOnFocusChangeListener(focusChangeListener);
   138     passwordEdit.addTextChangedListener(textChangedListener);
   139     passwordEdit.setOnEditorActionListener(editorActionListener);
   140     passwordEdit.setOnFocusChangeListener(focusChangeListener);
   141   }
   143   protected class FocusChangeListener implements OnFocusChangeListener {
   144     @Override
   145     public void onFocusChange(View v, boolean hasFocus) {
   146       if (hasFocus) {
   147         return;
   148       }
   149       updateButtonState();
   150     }
   151   }
   153   protected class EditorActionListener implements OnEditorActionListener {
   154     @Override
   155     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
   156       updateButtonState();
   157       return false;
   158     }
   159   }
   161   protected class TextChangedListener implements TextWatcher {
   162     @Override
   163     public void afterTextChanged(Editable s) {
   164       updateButtonState();
   165     }
   167     @Override
   168     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   169       // Do nothing.
   170     }
   172     @Override
   173     public void onTextChanged(CharSequence s, int start, int before, int count) {
   174       // Do nothing.
   175     }
   176   }
   178   protected boolean shouldButtonBeEnabled() {
   179     final String email = emailEdit.getText().toString();
   180     final String password = passwordEdit.getText().toString();
   182     boolean enabled =
   183         (email.length() > 0) &&
   184         Patterns.EMAIL_ADDRESS.matcher(email).matches() &&
   185         (password.length() >= minimumPasswordLength);
   186     return enabled;
   187   }
   189   protected boolean updateButtonState() {
   190     boolean enabled = shouldButtonBeEnabled();
   191     if (!enabled) {
   192       // The user needs to do something before you can interact with the button;
   193       // presumably that interaction will fix whatever error is shown.
   194       hideRemoteError();
   195     }
   196     if (enabled != button.isEnabled()) {
   197       Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
   198       button.setEnabled(enabled);
   199     }
   200     return enabled;
   201   }
   203   @Override
   204   public void showProgress() {
   205     progressBar.setVisibility(View.VISIBLE);
   206     button.setVisibility(View.INVISIBLE);
   207   }
   209   @Override
   210   public void dismissProgress() {
   211     progressBar.setVisibility(View.INVISIBLE);
   212     button.setVisibility(View.VISIBLE);
   213   }
   215   public Intent makeSuccessIntent(String email, LoginResponse result) {
   216     Intent successIntent;
   217     if (result.verified) {
   218       successIntent = new Intent(this, FxAccountVerifiedAccountActivity.class);
   219     } else {
   220       successIntent = new Intent(this, FxAccountConfirmAccountActivity.class);
   221     }
   222     // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
   223     // the soft keyboard not being shown for the started activity. Why, Android, why?
   224     successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
   225     return successIntent;
   226   }
   228   protected abstract class AddAccountDelegate implements RequestDelegate<LoginResponse> {
   229     public final String email;
   230     public final PasswordStretcher passwordStretcher;
   231     public final String serverURI;
   232     public final Map<String, Boolean> selectedEngines;
   234     public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
   235       this(email, passwordStretcher, serverURI, null);
   236     }
   238     public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI, Map<String, Boolean> selectedEngines) {
   239       if (email == null) {
   240         throw new IllegalArgumentException("email must not be null");
   241       }
   242       if (passwordStretcher == null) {
   243         throw new IllegalArgumentException("passwordStretcher must not be null");
   244       }
   245       if (serverURI == null) {
   246         throw new IllegalArgumentException("serverURI must not be null");
   247       }
   248       this.email = email;
   249       this.passwordStretcher = passwordStretcher;
   250       this.serverURI = serverURI;
   251       // selectedEngines can be null, which means don't write
   252       // userSelectedEngines to prefs. This makes any created meta/global record
   253       // have the default set of engines to sync.
   254       this.selectedEngines = selectedEngines;
   255     }
   257     @Override
   258     public void handleSuccess(LoginResponse result) {
   259       Logger.info(LOG_TAG, "Got success response; adding Android account.");
   261       // We're on the UI thread, but it's okay to create the account here.
   262       AndroidFxAccount fxAccount;
   263       try {
   264         final String profile = Constants.DEFAULT_PROFILE;
   265         final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
   266         // It is crucial that we use the email address provided by the server
   267         // (rather than whatever the user entered), because the user's keys are
   268         // wrapped and salted with the initial email they provided to
   269         // /create/account. Of course, we want to pass through what the user
   270         // entered locally as much as possible, so we create the Android account
   271         // with their entered email address, etc.
   272         // The passwordStretcher should have seen this email address before, so
   273         // we shouldn't be calculating the expensive stretch twice.
   274         byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
   275         byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
   276         State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken);
   277         fxAccount = AndroidFxAccount.addAndroidAccount(getApplicationContext(),
   278             email,
   279             profile,
   280             serverURI,
   281             tokenServerURI,
   282             state);
   283         if (fxAccount == null) {
   284           throw new RuntimeException("Could not add Android account.");
   285         }
   287         if (selectedEngines != null) {
   288           Logger.info(LOG_TAG, "User has selected engines; storing to prefs.");
   289           SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), selectedEngines);
   290         }
   291       } catch (Exception e) {
   292         handleError(e);
   293         return;
   294       }
   296       // For great debugging.
   297       if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
   298         fxAccount.dump();
   299       }
   301       // The GetStarted activity has called us and needs to return a result to the authenticator.
   302       final Intent intent = new Intent();
   303       intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
   304       intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
   305       // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
   306       setResult(RESULT_OK, intent);
   308       // Show success activity depending on verification status.
   309       Intent successIntent = makeSuccessIntent(email, result);
   310       startActivity(successIntent);
   311       finish();
   312     }
   313   }
   315   /**
   316    * Factory function that produces a new PasswordStretcher instance.
   317    *
   318    * @return PasswordStretcher instance.
   319    */
   320   protected PasswordStretcher makePasswordStretcher(String password) {
   321     return new QuickPasswordStretcher(password);
   322   }
   324   protected abstract static class GetAccountsAsyncTask extends AsyncTask<Void, Void, Account[]> {
   325     protected final Context context;
   327     public GetAccountsAsyncTask(Context context) {
   328       super();
   329       this.context = context;
   330     }
   332     @Override
   333     protected Account[] doInBackground(Void... params) {
   334       return AccountManager.get(context).getAccounts();
   335     }
   336   }
   338   /**
   339    * This updates UI, so needs to be done on the foreground thread.
   340    */
   341   protected void populateEmailAddressAutocomplete(Account[] accounts) {
   342     // First a set, since we don't want repeats.
   343     final Set<String> emails = new HashSet<String>();
   344     for (Account account : accounts) {
   345       if (!Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
   346         continue;
   347       }
   348       emails.add(account.name);
   349     }
   351     // And then sorted in alphabetical order.
   352     final String[] sortedEmails = emails.toArray(new String[0]);
   353     Arrays.sort(sortedEmails);
   355     final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails);
   356     emailEdit.setAdapter(adapter);
   357   }
   359   @Override
   360   public void onResume() {
   361     super.onResume();
   363     // Getting Accounts accesses databases on disk, so needs to be done on a
   364     // background thread.
   365     final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) {
   366       @Override
   367       public void onPostExecute(Account[] accounts) {
   368         populateEmailAddressAutocomplete(accounts);
   369       }
   370     };
   371     task.execute();
   372   }
   373 }

mercurial