Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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.sync.setup.activities;
7 import java.util.Locale;
9 import org.mozilla.gecko.R;
10 import org.mozilla.gecko.background.common.log.Logger;
11 import org.mozilla.gecko.sync.SyncConstants;
12 import org.mozilla.gecko.sync.ThreadPool;
13 import org.mozilla.gecko.sync.setup.Constants;
14 import org.mozilla.gecko.sync.setup.InvalidSyncKeyException;
15 import org.mozilla.gecko.sync.setup.SyncAccounts;
16 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
17 import org.mozilla.gecko.sync.setup.auth.AccountAuthenticator;
18 import org.mozilla.gecko.sync.setup.auth.AuthenticationResult;
20 import android.accounts.Account;
21 import android.accounts.AccountAuthenticatorActivity;
22 import android.accounts.AccountManager;
23 import android.app.ProgressDialog;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.text.Editable;
28 import android.text.TextWatcher;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.Window;
32 import android.view.WindowManager;
33 import android.widget.Button;
34 import android.widget.CheckBox;
35 import android.widget.CompoundButton;
36 import android.widget.CompoundButton.OnCheckedChangeListener;
37 import android.widget.EditText;
38 import android.widget.Toast;
40 public class AccountActivity extends AccountAuthenticatorActivity {
41 private final static String LOG_TAG = "AccountActivity";
43 private AccountManager mAccountManager;
44 private Context mContext;
45 private String username;
46 private String password;
47 private String key;
48 private String server = SyncConstants.DEFAULT_AUTH_SERVER;
50 // UI elements.
51 private EditText serverInput;
52 private EditText usernameInput;
53 private EditText passwordInput;
54 private EditText synckeyInput;
55 private CheckBox serverCheckbox;
56 private Button connectButton;
57 private Button cancelButton;
58 private ProgressDialog progressDialog;
60 private AccountAuthenticator accountAuthenticator;
62 @Override
63 public void onCreate(Bundle savedInstanceState) {
64 super.onCreate(savedInstanceState);
65 setContentView(R.layout.sync_account);
67 ActivityUtils.prepareLogging();
68 mContext = getApplicationContext();
69 Logger.debug(LOG_TAG, "AccountManager.get(" + mContext + ")");
70 mAccountManager = AccountManager.get(mContext);
72 // Set "screen on" flag.
73 Logger.debug(LOG_TAG, "Setting screen-on flag.");
74 Window w = getWindow();
75 w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
77 // Find UI elements.
78 usernameInput = (EditText) findViewById(R.id.usernameInput);
79 passwordInput = (EditText) findViewById(R.id.passwordInput);
80 synckeyInput = (EditText) findViewById(R.id.keyInput);
81 serverInput = (EditText) findViewById(R.id.serverInput);
83 TextWatcher inputValidator = makeInputValidator();
85 usernameInput.addTextChangedListener(inputValidator);
86 passwordInput.addTextChangedListener(inputValidator);
87 synckeyInput.addTextChangedListener(inputValidator);
88 serverInput.addTextChangedListener(inputValidator);
90 connectButton = (Button) findViewById(R.id.accountConnectButton);
91 cancelButton = (Button) findViewById(R.id.accountCancelButton);
92 serverCheckbox = (CheckBox) findViewById(R.id.checkbox_server);
94 serverCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
95 @Override
96 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
97 Logger.info(LOG_TAG, "Toggling checkbox: " + isChecked);
98 if (!isChecked) { // Clear server input.
99 serverInput.setVisibility(View.GONE);
100 findViewById(R.id.server_error).setVisibility(View.GONE);
101 serverInput.setText("");
102 } else {
103 serverInput.setVisibility(View.VISIBLE);
104 serverInput.setEnabled(true);
105 }
106 // Activate connectButton if necessary.
107 activateView(connectButton, validateInputs());
108 }
109 });
110 }
112 @Override
113 public void onResume() {
114 super.onResume();
115 ActivityUtils.prepareLogging();
116 clearCredentials();
117 usernameInput.requestFocus();
118 cancelButton.setOnClickListener(new OnClickListener() {
120 @Override
121 public void onClick(View v) {
122 cancelClickHandler(v);
123 }
125 });
126 }
128 public void cancelClickHandler(View target) {
129 finish();
130 }
132 public void cancelConnectHandler(View target) {
133 if (accountAuthenticator != null) {
134 accountAuthenticator.isCanceled = true;
135 accountAuthenticator = null;
136 }
137 displayVerifying(false);
138 activateView(connectButton, true);
139 clearCredentials();
140 usernameInput.requestFocus();
141 }
143 private void clearCredentials() {
144 // Only clear password. Re-typing the sync key or email is annoying.
145 passwordInput.setText("");
146 }
147 /*
148 * Get credentials on "Connect" and write to AccountManager, where it can be
149 * accessed by Fennec and Sync Service.
150 */
151 public void connectClickHandler(View target) {
152 Logger.debug(LOG_TAG, "connectClickHandler for view " + target);
153 // Validate sync key format.
154 try {
155 key = ActivityUtils.validateSyncKey(synckeyInput.getText().toString());
156 } catch (InvalidSyncKeyException e) {
157 // Toast: invalid sync key format.
158 Toast toast = Toast.makeText(mContext, R.string.sync_new_recoverykey_status_incorrect, Toast.LENGTH_LONG);
159 toast.show();
160 return;
161 }
162 username = usernameInput.getText().toString().toLowerCase(Locale.US);
163 password = passwordInput.getText().toString();
164 key = synckeyInput.getText().toString();
165 server = SyncConstants.DEFAULT_AUTH_SERVER;
167 if (serverCheckbox.isChecked()) {
168 String userServer = serverInput.getText().toString();
169 if (userServer != null) {
170 userServer = userServer.trim();
171 if (userServer.length() != 0) {
172 if (!userServer.startsWith("https://") &&
173 !userServer.startsWith("http://")) {
174 // Assume HTTPS if not specified.
175 userServer = "https://" + userServer;
176 serverInput.setText(userServer);
177 }
178 server = userServer;
179 }
180 }
181 }
183 clearErrors();
184 displayVerifying(true);
185 cancelButton.setOnClickListener(new OnClickListener() {
186 @Override
187 public void onClick(View v) {
188 cancelConnectHandler(v);
189 // Set cancel click handler to leave account setup.
190 cancelButton.setOnClickListener(new OnClickListener() {
191 public void onClick(View v) {
192 cancelClickHandler(v);
193 }
194 });
195 }
196 });
198 accountAuthenticator = new AccountAuthenticator(this);
199 accountAuthenticator.authenticate(server, username, password);
200 }
202 private TextWatcher makeInputValidator() {
203 return new TextWatcher() {
205 @Override
206 public void afterTextChanged(Editable s) {
207 activateView(connectButton, validateInputs());
208 }
210 @Override
211 public void beforeTextChanged(CharSequence s, int start, int count,
212 int after) {
213 }
215 @Override
216 public void onTextChanged(CharSequence s, int start, int before, int count) {
217 }
218 };
219 }
221 private boolean validateInputs() {
222 if (usernameInput.length() == 0 ||
223 passwordInput.length() == 0 ||
224 synckeyInput.length() == 0 ||
225 (serverCheckbox.isChecked() &&
226 serverInput.length() == 0)) {
227 return false;
228 }
229 return true;
230 }
232 /*
233 * Callback that handles auth based on success/failure
234 */
235 public void authCallback(final AuthenticationResult result) {
236 displayVerifying(false);
237 if (result != AuthenticationResult.SUCCESS) {
238 Logger.debug(LOG_TAG, "displayFailure()");
239 displayFailure(result);
240 return;
241 }
242 // Successful authentication. Create and add account to AccountManager.
243 SyncAccountParameters syncAccount = new SyncAccountParameters(
244 mContext, mAccountManager, username, key, password, server);
245 createAccountOnThread(syncAccount);
246 }
248 private void createAccountOnThread(final SyncAccountParameters syncAccount) {
249 ThreadPool.run(new Runnable() {
250 @Override
251 public void run() {
252 Account account = SyncAccounts.createSyncAccount(syncAccount);
253 boolean isSuccess = (account != null);
254 if (!isSuccess) {
255 setResult(RESULT_CANCELED);
256 runOnUiThread(new Runnable() {
257 @Override
258 public void run() {
259 displayFailure(AuthenticationResult.FAILURE_ACCOUNT);
260 }
261 });
262 return;
263 }
265 // Account created successfully.
266 clearErrors();
268 Bundle resultBundle = new Bundle();
269 resultBundle.putString(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username);
270 resultBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, SyncConstants.ACCOUNTTYPE_SYNC);
271 resultBundle.putString(AccountManager.KEY_AUTHTOKEN, SyncConstants.ACCOUNTTYPE_SYNC);
272 setAccountAuthenticatorResult(resultBundle);
274 setResult(RESULT_OK);
275 runOnUiThread(new Runnable() {
276 @Override
277 public void run() {
278 authSuccess();
279 }
280 });
281 }
282 });
283 }
285 private void displayVerifying(final boolean isVerifying) {
286 if (isVerifying) {
287 progressDialog = ProgressDialog.show(AccountActivity.this, "", getString(R.string.sync_verifying_label), true);
288 } else {
289 progressDialog.dismiss();
290 }
291 }
293 private void displayFailure(final AuthenticationResult result) {
294 runOnUiThread(new Runnable() {
295 @Override
296 public void run() {
297 Intent intent;
298 switch (result) {
299 case FAILURE_USERNAME:
300 // No such username. Don't leak whether the username exists.
301 case FAILURE_PASSWORD:
302 findViewById(R.id.cred_error).setVisibility(View.VISIBLE);
303 usernameInput.requestFocus();
304 break;
305 case FAILURE_SERVER:
306 findViewById(R.id.server_error).setVisibility(View.VISIBLE);
307 serverInput.requestFocus();
308 break;
309 case FAILURE_ACCOUNT:
310 intent = new Intent(mContext, SetupFailureActivity.class);
311 intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
312 intent.putExtra(Constants.INTENT_EXTRA_IS_ACCOUNTERROR, true);
313 startActivity(intent);
314 break;
315 case FAILURE_OTHER:
316 default:
317 // Display default error screen.
318 Logger.debug(LOG_TAG, "displaying default failure.");
319 intent = new Intent(mContext, SetupFailureActivity.class);
320 intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
321 startActivity(intent);
322 }
323 }
324 });
325 }
327 /**
328 * Feedback to user of account setup success.
329 */
330 public void authSuccess() {
331 // Display feedback of successful account setup.
332 Intent intent = new Intent(mContext, SetupSuccessActivity.class);
333 intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
334 startActivity(intent);
335 finish();
336 }
338 private void activateView(View view, boolean toActivate) {
339 view.setEnabled(toActivate);
340 view.setClickable(toActivate);
341 }
343 private void clearErrors() {
344 runOnUiThread(new Runnable() {
345 @Override
346 public void run() {
347 findViewById(R.id.cred_error).setVisibility(View.GONE);
348 findViewById(R.id.server_error).setVisibility(View.GONE);
349 }
350 });
351 }
352 }