Wed, 31 Dec 2014 06:09:35 +0100
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 }