1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/sync/setup/activities/AccountActivity.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,352 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.sync.setup.activities; 1.9 + 1.10 +import java.util.Locale; 1.11 + 1.12 +import org.mozilla.gecko.R; 1.13 +import org.mozilla.gecko.background.common.log.Logger; 1.14 +import org.mozilla.gecko.sync.SyncConstants; 1.15 +import org.mozilla.gecko.sync.ThreadPool; 1.16 +import org.mozilla.gecko.sync.setup.Constants; 1.17 +import org.mozilla.gecko.sync.setup.InvalidSyncKeyException; 1.18 +import org.mozilla.gecko.sync.setup.SyncAccounts; 1.19 +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; 1.20 +import org.mozilla.gecko.sync.setup.auth.AccountAuthenticator; 1.21 +import org.mozilla.gecko.sync.setup.auth.AuthenticationResult; 1.22 + 1.23 +import android.accounts.Account; 1.24 +import android.accounts.AccountAuthenticatorActivity; 1.25 +import android.accounts.AccountManager; 1.26 +import android.app.ProgressDialog; 1.27 +import android.content.Context; 1.28 +import android.content.Intent; 1.29 +import android.os.Bundle; 1.30 +import android.text.Editable; 1.31 +import android.text.TextWatcher; 1.32 +import android.view.View; 1.33 +import android.view.View.OnClickListener; 1.34 +import android.view.Window; 1.35 +import android.view.WindowManager; 1.36 +import android.widget.Button; 1.37 +import android.widget.CheckBox; 1.38 +import android.widget.CompoundButton; 1.39 +import android.widget.CompoundButton.OnCheckedChangeListener; 1.40 +import android.widget.EditText; 1.41 +import android.widget.Toast; 1.42 + 1.43 +public class AccountActivity extends AccountAuthenticatorActivity { 1.44 + private final static String LOG_TAG = "AccountActivity"; 1.45 + 1.46 + private AccountManager mAccountManager; 1.47 + private Context mContext; 1.48 + private String username; 1.49 + private String password; 1.50 + private String key; 1.51 + private String server = SyncConstants.DEFAULT_AUTH_SERVER; 1.52 + 1.53 + // UI elements. 1.54 + private EditText serverInput; 1.55 + private EditText usernameInput; 1.56 + private EditText passwordInput; 1.57 + private EditText synckeyInput; 1.58 + private CheckBox serverCheckbox; 1.59 + private Button connectButton; 1.60 + private Button cancelButton; 1.61 + private ProgressDialog progressDialog; 1.62 + 1.63 + private AccountAuthenticator accountAuthenticator; 1.64 + 1.65 + @Override 1.66 + public void onCreate(Bundle savedInstanceState) { 1.67 + super.onCreate(savedInstanceState); 1.68 + setContentView(R.layout.sync_account); 1.69 + 1.70 + ActivityUtils.prepareLogging(); 1.71 + mContext = getApplicationContext(); 1.72 + Logger.debug(LOG_TAG, "AccountManager.get(" + mContext + ")"); 1.73 + mAccountManager = AccountManager.get(mContext); 1.74 + 1.75 + // Set "screen on" flag. 1.76 + Logger.debug(LOG_TAG, "Setting screen-on flag."); 1.77 + Window w = getWindow(); 1.78 + w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1.79 + 1.80 + // Find UI elements. 1.81 + usernameInput = (EditText) findViewById(R.id.usernameInput); 1.82 + passwordInput = (EditText) findViewById(R.id.passwordInput); 1.83 + synckeyInput = (EditText) findViewById(R.id.keyInput); 1.84 + serverInput = (EditText) findViewById(R.id.serverInput); 1.85 + 1.86 + TextWatcher inputValidator = makeInputValidator(); 1.87 + 1.88 + usernameInput.addTextChangedListener(inputValidator); 1.89 + passwordInput.addTextChangedListener(inputValidator); 1.90 + synckeyInput.addTextChangedListener(inputValidator); 1.91 + serverInput.addTextChangedListener(inputValidator); 1.92 + 1.93 + connectButton = (Button) findViewById(R.id.accountConnectButton); 1.94 + cancelButton = (Button) findViewById(R.id.accountCancelButton); 1.95 + serverCheckbox = (CheckBox) findViewById(R.id.checkbox_server); 1.96 + 1.97 + serverCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() { 1.98 + @Override 1.99 + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1.100 + Logger.info(LOG_TAG, "Toggling checkbox: " + isChecked); 1.101 + if (!isChecked) { // Clear server input. 1.102 + serverInput.setVisibility(View.GONE); 1.103 + findViewById(R.id.server_error).setVisibility(View.GONE); 1.104 + serverInput.setText(""); 1.105 + } else { 1.106 + serverInput.setVisibility(View.VISIBLE); 1.107 + serverInput.setEnabled(true); 1.108 + } 1.109 + // Activate connectButton if necessary. 1.110 + activateView(connectButton, validateInputs()); 1.111 + } 1.112 + }); 1.113 + } 1.114 + 1.115 + @Override 1.116 + public void onResume() { 1.117 + super.onResume(); 1.118 + ActivityUtils.prepareLogging(); 1.119 + clearCredentials(); 1.120 + usernameInput.requestFocus(); 1.121 + cancelButton.setOnClickListener(new OnClickListener() { 1.122 + 1.123 + @Override 1.124 + public void onClick(View v) { 1.125 + cancelClickHandler(v); 1.126 + } 1.127 + 1.128 + }); 1.129 + } 1.130 + 1.131 + public void cancelClickHandler(View target) { 1.132 + finish(); 1.133 + } 1.134 + 1.135 + public void cancelConnectHandler(View target) { 1.136 + if (accountAuthenticator != null) { 1.137 + accountAuthenticator.isCanceled = true; 1.138 + accountAuthenticator = null; 1.139 + } 1.140 + displayVerifying(false); 1.141 + activateView(connectButton, true); 1.142 + clearCredentials(); 1.143 + usernameInput.requestFocus(); 1.144 + } 1.145 + 1.146 + private void clearCredentials() { 1.147 + // Only clear password. Re-typing the sync key or email is annoying. 1.148 + passwordInput.setText(""); 1.149 + } 1.150 + /* 1.151 + * Get credentials on "Connect" and write to AccountManager, where it can be 1.152 + * accessed by Fennec and Sync Service. 1.153 + */ 1.154 + public void connectClickHandler(View target) { 1.155 + Logger.debug(LOG_TAG, "connectClickHandler for view " + target); 1.156 + // Validate sync key format. 1.157 + try { 1.158 + key = ActivityUtils.validateSyncKey(synckeyInput.getText().toString()); 1.159 + } catch (InvalidSyncKeyException e) { 1.160 + // Toast: invalid sync key format. 1.161 + Toast toast = Toast.makeText(mContext, R.string.sync_new_recoverykey_status_incorrect, Toast.LENGTH_LONG); 1.162 + toast.show(); 1.163 + return; 1.164 + } 1.165 + username = usernameInput.getText().toString().toLowerCase(Locale.US); 1.166 + password = passwordInput.getText().toString(); 1.167 + key = synckeyInput.getText().toString(); 1.168 + server = SyncConstants.DEFAULT_AUTH_SERVER; 1.169 + 1.170 + if (serverCheckbox.isChecked()) { 1.171 + String userServer = serverInput.getText().toString(); 1.172 + if (userServer != null) { 1.173 + userServer = userServer.trim(); 1.174 + if (userServer.length() != 0) { 1.175 + if (!userServer.startsWith("https://") && 1.176 + !userServer.startsWith("http://")) { 1.177 + // Assume HTTPS if not specified. 1.178 + userServer = "https://" + userServer; 1.179 + serverInput.setText(userServer); 1.180 + } 1.181 + server = userServer; 1.182 + } 1.183 + } 1.184 + } 1.185 + 1.186 + clearErrors(); 1.187 + displayVerifying(true); 1.188 + cancelButton.setOnClickListener(new OnClickListener() { 1.189 + @Override 1.190 + public void onClick(View v) { 1.191 + cancelConnectHandler(v); 1.192 + // Set cancel click handler to leave account setup. 1.193 + cancelButton.setOnClickListener(new OnClickListener() { 1.194 + public void onClick(View v) { 1.195 + cancelClickHandler(v); 1.196 + } 1.197 + }); 1.198 + } 1.199 + }); 1.200 + 1.201 + accountAuthenticator = new AccountAuthenticator(this); 1.202 + accountAuthenticator.authenticate(server, username, password); 1.203 + } 1.204 + 1.205 + private TextWatcher makeInputValidator() { 1.206 + return new TextWatcher() { 1.207 + 1.208 + @Override 1.209 + public void afterTextChanged(Editable s) { 1.210 + activateView(connectButton, validateInputs()); 1.211 + } 1.212 + 1.213 + @Override 1.214 + public void beforeTextChanged(CharSequence s, int start, int count, 1.215 + int after) { 1.216 + } 1.217 + 1.218 + @Override 1.219 + public void onTextChanged(CharSequence s, int start, int before, int count) { 1.220 + } 1.221 + }; 1.222 + } 1.223 + 1.224 + private boolean validateInputs() { 1.225 + if (usernameInput.length() == 0 || 1.226 + passwordInput.length() == 0 || 1.227 + synckeyInput.length() == 0 || 1.228 + (serverCheckbox.isChecked() && 1.229 + serverInput.length() == 0)) { 1.230 + return false; 1.231 + } 1.232 + return true; 1.233 + } 1.234 + 1.235 + /* 1.236 + * Callback that handles auth based on success/failure 1.237 + */ 1.238 + public void authCallback(final AuthenticationResult result) { 1.239 + displayVerifying(false); 1.240 + if (result != AuthenticationResult.SUCCESS) { 1.241 + Logger.debug(LOG_TAG, "displayFailure()"); 1.242 + displayFailure(result); 1.243 + return; 1.244 + } 1.245 + // Successful authentication. Create and add account to AccountManager. 1.246 + SyncAccountParameters syncAccount = new SyncAccountParameters( 1.247 + mContext, mAccountManager, username, key, password, server); 1.248 + createAccountOnThread(syncAccount); 1.249 + } 1.250 + 1.251 + private void createAccountOnThread(final SyncAccountParameters syncAccount) { 1.252 + ThreadPool.run(new Runnable() { 1.253 + @Override 1.254 + public void run() { 1.255 + Account account = SyncAccounts.createSyncAccount(syncAccount); 1.256 + boolean isSuccess = (account != null); 1.257 + if (!isSuccess) { 1.258 + setResult(RESULT_CANCELED); 1.259 + runOnUiThread(new Runnable() { 1.260 + @Override 1.261 + public void run() { 1.262 + displayFailure(AuthenticationResult.FAILURE_ACCOUNT); 1.263 + } 1.264 + }); 1.265 + return; 1.266 + } 1.267 + 1.268 + // Account created successfully. 1.269 + clearErrors(); 1.270 + 1.271 + Bundle resultBundle = new Bundle(); 1.272 + resultBundle.putString(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username); 1.273 + resultBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, SyncConstants.ACCOUNTTYPE_SYNC); 1.274 + resultBundle.putString(AccountManager.KEY_AUTHTOKEN, SyncConstants.ACCOUNTTYPE_SYNC); 1.275 + setAccountAuthenticatorResult(resultBundle); 1.276 + 1.277 + setResult(RESULT_OK); 1.278 + runOnUiThread(new Runnable() { 1.279 + @Override 1.280 + public void run() { 1.281 + authSuccess(); 1.282 + } 1.283 + }); 1.284 + } 1.285 + }); 1.286 + } 1.287 + 1.288 + private void displayVerifying(final boolean isVerifying) { 1.289 + if (isVerifying) { 1.290 + progressDialog = ProgressDialog.show(AccountActivity.this, "", getString(R.string.sync_verifying_label), true); 1.291 + } else { 1.292 + progressDialog.dismiss(); 1.293 + } 1.294 + } 1.295 + 1.296 + private void displayFailure(final AuthenticationResult result) { 1.297 + runOnUiThread(new Runnable() { 1.298 + @Override 1.299 + public void run() { 1.300 + Intent intent; 1.301 + switch (result) { 1.302 + case FAILURE_USERNAME: 1.303 + // No such username. Don't leak whether the username exists. 1.304 + case FAILURE_PASSWORD: 1.305 + findViewById(R.id.cred_error).setVisibility(View.VISIBLE); 1.306 + usernameInput.requestFocus(); 1.307 + break; 1.308 + case FAILURE_SERVER: 1.309 + findViewById(R.id.server_error).setVisibility(View.VISIBLE); 1.310 + serverInput.requestFocus(); 1.311 + break; 1.312 + case FAILURE_ACCOUNT: 1.313 + intent = new Intent(mContext, SetupFailureActivity.class); 1.314 + intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION); 1.315 + intent.putExtra(Constants.INTENT_EXTRA_IS_ACCOUNTERROR, true); 1.316 + startActivity(intent); 1.317 + break; 1.318 + case FAILURE_OTHER: 1.319 + default: 1.320 + // Display default error screen. 1.321 + Logger.debug(LOG_TAG, "displaying default failure."); 1.322 + intent = new Intent(mContext, SetupFailureActivity.class); 1.323 + intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION); 1.324 + startActivity(intent); 1.325 + } 1.326 + } 1.327 + }); 1.328 + } 1.329 + 1.330 + /** 1.331 + * Feedback to user of account setup success. 1.332 + */ 1.333 + public void authSuccess() { 1.334 + // Display feedback of successful account setup. 1.335 + Intent intent = new Intent(mContext, SetupSuccessActivity.class); 1.336 + intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION); 1.337 + startActivity(intent); 1.338 + finish(); 1.339 + } 1.340 + 1.341 + private void activateView(View view, boolean toActivate) { 1.342 + view.setEnabled(toActivate); 1.343 + view.setClickable(toActivate); 1.344 + } 1.345 + 1.346 + private void clearErrors() { 1.347 + runOnUiThread(new Runnable() { 1.348 + @Override 1.349 + public void run() { 1.350 + findViewById(R.id.cred_error).setVisibility(View.GONE); 1.351 + findViewById(R.id.server_error).setVisibility(View.GONE); 1.352 + } 1.353 + }); 1.354 + } 1.355 +}