From 641ae4535828028a19bf7989c75d71caf75ec2dd Mon Sep 17 00:00:00 2001 From: Andrew Stadler Date: Wed, 11 Aug 2010 16:07:26 -0700 Subject: [PATCH] Fragmentize Exchange setup Change-Id: Ib331d9a7f7be7acbfe355d6a03d9ae07af0cc627 --- res/layout/account_setup_exchange.xml | 70 +-- .../account_setup_exchange_fragment.xml | 82 ++++ .../activity/setup/AccountSetupExchange.java | 331 +++----------- .../setup/AccountSetupExchangeFragment.java | 406 ++++++++++++++++++ .../setup/AccountSetupExchangeTests.java | 15 +- 5 files changed, 556 insertions(+), 348 deletions(-) create mode 100644 res/layout/account_setup_exchange_fragment.xml create mode 100644 src/com/android/email/activity/setup/AccountSetupExchangeFragment.java diff --git a/res/layout/account_setup_exchange.xml b/res/layout/account_setup_exchange.xml index 68643249d..6b1f666b3 100644 --- a/res/layout/account_setup_exchange.xml +++ b/res/layout/account_setup_exchange.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,71 +25,13 @@ android:layout_height="wrap_content" android:orientation="vertical" > - - - - - - - - - - - - - - + /> + + + + + + + + + + + + + + + + diff --git a/src/com/android/email/activity/setup/AccountSetupExchange.java b/src/com/android/email/activity/setup/AccountSetupExchange.java index a0151d479..b8a03194c 100644 --- a/src/com/android/email/activity/setup/AccountSetupExchange.java +++ b/src/com/android/email/activity/setup/AccountSetupExchange.java @@ -16,38 +16,20 @@ package com.android.email.activity.setup; -import com.android.email.AccountBackupRestore; -import com.android.email.ExchangeUtils; import com.android.email.R; -import com.android.email.Utility; import com.android.email.SecurityPolicy.PolicySet; +import com.android.email.Utility; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.HostAuth; import com.android.email.service.EmailServiceProxy; -import com.android.exchange.SyncManager; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; -import android.os.RemoteException; -import android.text.Editable; -import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.CompoundButton.OnCheckedChangeListener; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; /** * Provides generic setup for Exchange accounts. The following fields are supported: @@ -83,23 +65,16 @@ import java.net.URISyntaxException; * Proceed to options screen * finish() (removes self from back stack) * - * NOTE: The manifest for this activity has it ignore config changes, because + * TODO: The manifest for this activity has it ignore config changes, because * we don't want to restart on every orientation - this would launch autodiscover again. * Do not attempt to define orientation-specific resources, they won't be loaded. + * What we really need here is a more "sticky" way to prevent that problem. */ -public class AccountSetupExchange extends AccountSetupActivity implements OnClickListener, - OnCheckedChangeListener { - private final static int DIALOG_DUPLICATE_ACCOUNT = 1; - - private EditText mUsernameView; - private EditText mPasswordView; - private EditText mServerView; - private CheckBox mSslSecurityView; - private CheckBox mTrustCertificatesView; +public class AccountSetupExchange extends AccountSetupActivity + implements OnClickListener, AccountSetupExchangeFragment.Callback { + /* package */ AccountSetupExchangeFragment mFragment; private Button mNextButton; - private String mCacheLoginCredential; - private String mDuplicateAccountName; public static void actionIncomingSettings(Activity fromActivity, int mode, Account acct) { SetupData.init(mode, acct); @@ -123,165 +98,38 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic super.onCreate(savedInstanceState); setContentView(R.layout.account_setup_exchange); - mUsernameView = (EditText) findViewById(R.id.account_username); - mPasswordView = (EditText) findViewById(R.id.account_password); - mServerView = (EditText) findViewById(R.id.account_server); - mSslSecurityView = (CheckBox) findViewById(R.id.account_ssl); - mSslSecurityView.setOnCheckedChangeListener(this); - mTrustCertificatesView = (CheckBox) findViewById(R.id.account_trust_certificates); - + mFragment = (AccountSetupExchangeFragment) findFragmentById(R.id.setup_exchange_fragment); mNextButton = (Button)findViewById(R.id.next); mNextButton.setOnClickListener(this); - /* - * Calls validateFields() which enables or disables the Next button - * based on the fields' validity. - */ - TextWatcher validationTextWatcher = new TextWatcher() { - public void afterTextChanged(Editable s) { - validateFields(); - } + mFragment.setCallback(this); - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + startAutoDiscover(); + } - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }; - mUsernameView.addTextChangedListener(validationTextWatcher); - mPasswordView.addTextChangedListener(validationTextWatcher); - mServerView.addTextChangedListener(validationTextWatcher); + /** + * If the conditions are right, launch the autodiscover activity. If it succeeds (even + * partially) it will prefill the setup fields and we can proceed as if the user entered them. + * + * Conditions for skipping: + * Editing existing account + * AutoDiscover blocked (used for unit testing only) + * Username or password not entered yet + */ + private void startAutoDiscover() { + if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT + || !SetupData.isAllowAutodiscover()) { + return; + } Account account = SetupData.getAccount(); - loadFields(account); - validateFields(); - // If we've got a username and password and we're NOT editing, try autodiscover String username = account.mHostAuthRecv.mLogin; String password = account.mHostAuthRecv.mPassword; Intent intent = getIntent(); - if (username != null && password != null && - SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { - // NOTE: Disabling AutoDiscover is only used in unit tests - if (SetupData.isAllowAutodiscover()) { - AccountSetupCheckSettings.actionAutoDiscover(this, account.mEmailAddress, password); - } + if (username != null && password != null) { + AccountSetupCheckSettings.actionAutoDiscover(this, account.mEmailAddress, password); } - - //EXCHANGE-REMOVE-SECTION-START - // Show device ID - try { - ((TextView) findViewById(R.id.device_id)).setText(SyncManager.getDeviceId(this)); - } catch (IOException ignore) { - // There's nothing we can do here... - } - //EXCHANGE-REMOVE-SECTION-END - } - - private boolean usernameFieldValid(EditText usernameView) { - return Utility.isTextViewNotEmpty(usernameView) && - !usernameView.getText().toString().equals("\\"); - } - - /** - * Prepare a cached dialog with current values (e.g. account name) - */ - @Override - public Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_DUPLICATE_ACCOUNT: - return new AlertDialog.Builder(this) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.account_duplicate_dlg_title) - .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, - mDuplicateAccountName)) - .setPositiveButton(R.string.okay_action, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dismissDialog(DIALOG_DUPLICATE_ACCOUNT); - } - }) - .create(); - } - return null; - } - - /** - * Update a cached dialog with current values (e.g. account name) - */ - @Override - public void onPrepareDialog(int id, Dialog dialog) { - switch (id) { - case DIALOG_DUPLICATE_ACCOUNT: - if (mDuplicateAccountName != null) { - AlertDialog alert = (AlertDialog) dialog; - alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, - mDuplicateAccountName)); - } - break; - } - } - - /** - * Copy mAccount's values into UI fields - */ - /* package */ void loadFields(Account account) { - HostAuth hostAuth = account.mHostAuthRecv; - - String userName = hostAuth.mLogin; - if (userName != null) { - // Add a backslash to the start of the username, but only if the username has no - // backslash in it. - if (userName.indexOf('\\') < 0) { - userName = "\\" + userName; - } - mUsernameView.setText(userName); - } - - if (hostAuth.mPassword != null) { - mPasswordView.setText(hostAuth.mPassword); - } - - String protocol = hostAuth.mProtocol; - if (protocol == null || !protocol.startsWith("eas")) { - throw new Error("Unknown account type: " + account.getStoreUri(this)); - } - - if (hostAuth.mAddress != null) { - mServerView.setText(hostAuth.mAddress); - } - - boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); - boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); - mSslSecurityView.setChecked(ssl); - mTrustCertificatesView.setChecked(trustCertificates); - mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE); - } - - /** - * Check the values in the fields and decide if it makes sense to enable the "next" button - * NOTE: Does it make sense to extract & combine with similar code in AccountSetupIncoming? - * @return true if all fields are valid, false if fields are incomplete - */ - private boolean validateFields() { - boolean enabled = usernameFieldValid(mUsernameView) - && Utility.isTextViewNotEmpty(mPasswordView) - && Utility.isTextViewNotEmpty(mServerView); - if (enabled) { - try { - URI uri = getUri(); - } catch (URISyntaxException use) { - enabled = false; - } - } - mNextButton.setEnabled(enabled); - Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128); - return enabled; - } - - private void doOptions(PolicySet policySet) { - AccountSetupOptions.actionOptions(this); - finish(); } /** @@ -306,36 +154,14 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic } /** - * Process activity result when validating existing account + * Process activity result when validating existing account. If OK, save and finish; + * otherwise simply remain in activity for further editing. */ private void doActivityResultValidateExistingAccount(int resultCode, Intent data) { - Account account = SetupData.getAccount(); if (resultCode == RESULT_OK) { - if (account.isSaved()) { - // Account.update will NOT save the HostAuth's - account.update(this, account.toContentValues()); - account.mHostAuthRecv.update(this, - account.mHostAuthRecv.toContentValues()); - account.mHostAuthSend.update(this, - account.mHostAuthSend.toContentValues()); - if (account.mHostAuthRecv.mProtocol.equals("eas")) { - // For EAS, notify SyncManager that the password has changed - try { - ExchangeUtils.getExchangeEmailService(this, null) - .hostChanged(account.mId); - } catch (RemoteException e) { - // Nothing to be done if this fails - } - } - } else { - // Account.save will save the HostAuth's - account.save(this); - } - // Update the backup (side copy) of the accounts - AccountBackupRestore.backupAccounts(this); + mFragment.saveSettingsAfterEdit(); finish(); } - // else (resultCode not OK) - just return into this activity for further editing } /** @@ -349,7 +175,8 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic ps = (PolicySet)data.getParcelableExtra( EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET); } - doOptions(ps); + AccountSetupOptions.actionOptions(this); + finish(); } else if (resultCode == AccountSetupCheckSettings.RESULT_SECURITY_REQUIRED_USER_CANCEL) { finish(); } @@ -357,7 +184,7 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic } /** - * Process activity result when validating new account + * Process activity result when provisioning new account via autodiscovery */ private void doActivityResultAutoDiscoverNewAccount(int resultCode, Intent data) { // If authentication failed, exit immediately (to re-enter credentials) @@ -371,94 +198,38 @@ public class AccountSetupExchange extends AccountSetupActivity implements OnClic Parcelable p = data.getParcelableExtra("HostAuth"); if (p != null) { HostAuth hostAuth = (HostAuth)p; - Account account = SetupData.getAccount(); - account.mHostAuthSend = hostAuth; - account.mHostAuthRecv = hostAuth; - loadFields(account); - if (validateFields()) { + boolean valid = mFragment.setHostAuthFromAutodiscover(hostAuth); + if (valid) { // "click" next to launch server verification - onNext(); + mFragment.onNext(); } } } // Otherwise, proceed into this activity for manual setup } - /** - * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's - * a problem with the user input. - * @return a URI built from the account setup fields - */ - private URI getUri() throws URISyntaxException { - boolean sslRequired = mSslSecurityView.isChecked(); - boolean trustCertificates = mTrustCertificatesView.isChecked(); - String scheme = (sslRequired) - ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") - : "eas"; - String userName = mUsernameView.getText().toString().trim(); - // Remove a leading backslash, if there is one, since we now automatically put one at - // the start of the username field - if (userName.startsWith("\\")) { - userName = userName.substring(1); - } - mCacheLoginCredential = userName; - String userInfo = userName + ":" + mPasswordView.getText().toString().trim(); - String host = mServerView.getText().toString().trim(); - String path = null; - - URI uri = new URI( - scheme, - userInfo, - host, - 0, - path, - null, - null); - - return uri; - } - - /** - * Note, in EAS, store & sender are the same, so we always populate them together - */ - private void onNext() { - try { - URI uri = getUri(); - Account setupAccount = SetupData.getAccount(); - setupAccount.setStoreUri(this, uri.toString()); - setupAccount.setSenderUri(this, uri.toString()); - - // Stop here if the login credentials duplicate an existing account - // (unless they duplicate the existing account, as they of course will) - Account account = Utility.findExistingAccount(this, setupAccount.mId, - uri.getHost(), mCacheLoginCredential); - if (account != null) { - mDuplicateAccountName = account.mDisplayName; - this.showDialog(DIALOG_DUPLICATE_ACCOUNT); - return; - } - } catch (URISyntaxException use) { - /* - * It's unrecoverable if we cannot create a URI from components that - * we validated to be safe. - */ - throw new Error(use); - } - - AccountSetupCheckSettings.actionCheckSettings(this, SetupData.CHECK_INCOMING); - } - public void onClick(View v) { switch (v.getId()) { case R.id.next: - onNext(); + mFragment.onNext(); break; } } - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (buttonView.getId() == R.id.account_ssl) { - mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE); - } + /** + * Implements AccountSetupIncomingFragment.Listener + */ + public void onProceedNext() { + AccountSetupCheckSettings.actionCheckSettings(this, SetupData.CHECK_INCOMING); + } + + /** + * Implements AccountSetupIncomingFragment.Listener + */ + public void onEnableProceedButtons(boolean enabled) { + mNextButton.setEnabled(enabled); + // Dim the next button's icon to 50% if the button is disabled. + // TODO this can probably be done with a stateful drawable. (check android:state_enabled) + Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128); } } diff --git a/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java new file mode 100644 index 000000000..45e81e1d8 --- /dev/null +++ b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.email.activity.setup; + +import com.android.email.AccountBackupRestore; +import com.android.email.Email; +import com.android.email.ExchangeUtils; +import com.android.email.R; +import com.android.email.Utility; +import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.HostAuth; +import com.android.exchange.SyncManager; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.TextView; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Provides generic setup for Exchange accounts. + * + * TODO: The manifest for this activity has it ignore config changes, because + * we don't want to restart on every orientation - this would launch autodiscover again. + * Do not attempt to define orientation-specific resources, they won't be loaded. + * What we really need here is a more "sticky" way to prevent that problem. + */ +public class AccountSetupExchangeFragment extends Fragment implements OnCheckedChangeListener { + + private final static String STATE_KEY_CREDENTIAL = + "AccountSetupExchangeFragment.loginCredential"; + + private EditText mUsernameView; + private EditText mPasswordView; + private EditText mServerView; + private CheckBox mSslSecurityView; + private CheckBox mTrustCertificatesView; + + // Support for lifecycle + private Context mContext; + private Callback mCallback = EmptyCallback.INSTANCE; + private boolean mStarted; + private boolean mLoaded; + private String mCacheLoginCredential; + + /** + * Callback interface that owning activities must implement + */ + public interface Callback { + public void onEnableProceedButtons(boolean enable); + public void onProceedNext(); + } + + private static class EmptyCallback implements Callback { + public static final Callback INSTANCE = new EmptyCallback(); + @Override public void onProceedNext() { } + @Override public void onEnableProceedButtons(boolean enable) { } + } + + /** + * Called to do initial creation of a fragment. This is called after + * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreate"); + } + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); + } + View view = inflater.inflate(R.layout.account_setup_exchange_fragment, container, false); + Context context = getActivity(); + + mUsernameView = (EditText) view.findViewById(R.id.account_username); + mPasswordView = (EditText) view.findViewById(R.id.account_password); + mServerView = (EditText) view.findViewById(R.id.account_server); + mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); + mSslSecurityView.setOnCheckedChangeListener(this); + mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); + + // Calls validateFields() which enables or disables the Next button + // based on the fields' validity. + TextWatcher validationTextWatcher = new TextWatcher() { + public void afterTextChanged(Editable s) { + validateFields(); + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { } + }; + mUsernameView.addTextChangedListener(validationTextWatcher); + mPasswordView.addTextChangedListener(validationTextWatcher); + mServerView.addTextChangedListener(validationTextWatcher); + + //EXCHANGE-REMOVE-SECTION-START + // Show device ID + try { + String deviceId = SyncManager.getDeviceId(context); + ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); + } catch (IOException ignore) { + // There's nothing we can do here... + } + //EXCHANGE-REMOVE-SECTION-END + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); + } + super.onActivityCreated(savedInstanceState); + } + + /** + * Called when the Fragment is visible to the user. + */ + @Override + public void onStart() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStart"); + } + super.onStart(); + mStarted = true; + if (!mLoaded) { + loadSettings(SetupData.getAccount()); + } + } + + /** + * Called when the fragment is visible to the user and actively running. + */ + @Override + public void onResume() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onResume"); + } + super.onResume(); + validateFields(); + } + + @Override + public void onPause() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onPause"); + } + super.onPause(); + } + + /** + * Called when the Fragment is no longer started. + */ + @Override + public void onStop() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStop"); + } + super.onStop(); + mStarted = false; + } + + /** + * Called when the fragment is no longer in use. + */ + @Override + public void onDestroy() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onDestroy"); + } + super.onDestroy(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState"); + } + super.onSaveInstanceState(outState); + + outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); + } + + /** + * Activity provides callbacks here. This also triggers loading and setting up the UX + */ + public void setCallback(Callback callback) { + mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; + mContext = getActivity(); + if (mStarted && !mLoaded) { + loadSettings(SetupData.getAccount()); + } + } + + /** + * Load the current settings into the UI + * + * @return true if the loaded values pass validation + */ + /* package */ boolean loadSettings(Account account) { + HostAuth hostAuth = account.mHostAuthRecv; + + String userName = hostAuth.mLogin; + if (userName != null) { + // Add a backslash to the start of the username, but only if the username has no + // backslash in it. + if (userName.indexOf('\\') < 0) { + userName = "\\" + userName; + } + mUsernameView.setText(userName); + } + + if (hostAuth.mPassword != null) { + mPasswordView.setText(hostAuth.mPassword); + } + + String protocol = hostAuth.mProtocol; + if (protocol == null || !protocol.startsWith("eas")) { + throw new Error("Unknown account type: " + account.getStoreUri(mContext)); + } + + if (hostAuth.mAddress != null) { + mServerView.setText(hostAuth.mAddress); + } + + boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); + boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); + mSslSecurityView.setChecked(ssl); + mTrustCertificatesView.setChecked(trustCertificates); + mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE); + + return validateFields(); + } + + private boolean usernameFieldValid(EditText usernameView) { + return Utility.isTextViewNotEmpty(usernameView) && + !usernameView.getText().toString().equals("\\"); + } + + /** + * Check the values in the fields and decide if it makes sense to enable the "next" button + * @return true if all fields are valid, false if any fields are incomplete + */ + private boolean validateFields() { + boolean enabled = usernameFieldValid(mUsernameView) + && Utility.isTextViewNotEmpty(mPasswordView) + && Utility.isTextViewNotEmpty(mServerView); + if (enabled) { + try { + URI uri = getUri(); + } catch (URISyntaxException use) { + enabled = false; + } + } + mCallback.onEnableProceedButtons(enabled); + return enabled; + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.getId() == R.id.account_ssl) { + mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE); + } + } + /** + * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. + * + * TODO: Was the !isSaved() logic ever actually used? + */ + public void saveSettingsAfterEdit() { + Account account = SetupData.getAccount(); + if (account.isSaved()) { + // Account.update will NOT save the HostAuth's + account.update(mContext, account.toContentValues()); + account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); + account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); + if (account.mHostAuthRecv.mProtocol.equals("eas")) { + // For EAS, notify SyncManager that the password has changed + try { + ExchangeUtils.getExchangeEmailService(mContext, null).hostChanged(account.mId); + } catch (RemoteException e) { + // Nothing to be done if this fails + } + } + } else { + // Account.save will save the HostAuth's + account.save(mContext); + } + // Update the backup (side copy) of the accounts + AccountBackupRestore.backupAccounts(mContext); + } + + /** + * Entry point from Activity after entering new settings and verifying them. For setup mode. + */ + public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { + Account account = SetupData.getAccount(); + account.mHostAuthSend = newHostAuth; + account.mHostAuthRecv = newHostAuth; + return loadSettings(account); + } + + /** + * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's + * a problem with the user input. + * @return a URI built from the account setup fields + */ + private URI getUri() throws URISyntaxException { + boolean sslRequired = mSslSecurityView.isChecked(); + boolean trustCertificates = mTrustCertificatesView.isChecked(); + String scheme = (sslRequired) + ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") + : "eas"; + String userName = mUsernameView.getText().toString().trim(); + // Remove a leading backslash, if there is one, since we now automatically put one at + // the start of the username field + if (userName.startsWith("\\")) { + userName = userName.substring(1); + } + mCacheLoginCredential = userName; + String userInfo = userName + ":" + mPasswordView.getText().toString().trim(); + String host = mServerView.getText().toString().trim(); + String path = null; + + URI uri = new URI( + scheme, + userInfo, + host, + 0, + path, + null, + null); + + return uri; + } + + /** + * Entry point from Activity, when "next" button is clicked + */ + public void onNext() { + try { + URI uri = getUri(); + Account setupAccount = SetupData.getAccount(); + setupAccount.setStoreUri(mContext, uri.toString()); + setupAccount.setSenderUri(mContext, uri.toString()); + + // Stop here if the login credentials duplicate an existing account + // (unless they duplicate the existing account, as they of course will) + Account account = Utility.findExistingAccount(mContext, setupAccount.mId, + uri.getHost(), mCacheLoginCredential); + if (account != null) { + DuplicateAccountDialogFragment dialogFragment = + new DuplicateAccountDialogFragment(account.mDisplayName, getId()); + dialogFragment.show(getActivity(), DuplicateAccountDialogFragment.TAG); + return; + } + } catch (URISyntaxException use) { + /* + * It's unrecoverable if we cannot create a URI from components that + * we validated to be safe. + */ + throw new Error(use); + } + + mCallback.onProceedNext(); + } +} diff --git a/tests/src/com/android/email/activity/setup/AccountSetupExchangeTests.java b/tests/src/com/android/email/activity/setup/AccountSetupExchangeTests.java index d6b3a0921..5a4c6a0e2 100644 --- a/tests/src/com/android/email/activity/setup/AccountSetupExchangeTests.java +++ b/tests/src/com/android/email/activity/setup/AccountSetupExchangeTests.java @@ -128,12 +128,12 @@ public class AccountSetupExchangeTests extends } /** - * Test aspects of loadFields() + * Test aspects of loadSettings() * * TODO: More cases */ @UiThreadTest - public void testLoadFields() { + public void testLoadSettings() { // The default URI has no SSL and no "trust" getActivityAndFields(); assertFalse(mSslRequiredCheckbox.isChecked()); @@ -148,7 +148,8 @@ public class AccountSetupExchangeTests extends "eas", "hostauth", 1, false, mActivity.getBaseContext()); account.mHostAuthRecv.mFlags |= HostAuth.FLAG_SSL; account.mHostAuthRecv.mFlags &= ~HostAuth.FLAG_TRUST_ALL_CERTIFICATES; - mActivity.loadFields(account); + boolean loadResult = mActivity.mFragment.loadSettings(account); + assertTrue(loadResult); assertTrue(mSslRequiredCheckbox.isChecked()); assertFalse(mTrustAllCertificatesCheckbox.isChecked()); assertTrue(mTrustAllCertificatesCheckbox.getVisibility() == View.VISIBLE); @@ -156,10 +157,16 @@ public class AccountSetupExchangeTests extends // Setup host auth with variants of SSL enabled and check. This also enables the // "trust certificates" checkbox (not checked, but visible now). account.mHostAuthRecv.mFlags |= HostAuth.FLAG_TRUST_ALL_CERTIFICATES; - mActivity.loadFields(account); + loadResult = mActivity.mFragment.loadSettings(account); + assertTrue(loadResult); assertTrue(mSslRequiredCheckbox.isChecked()); assertTrue(mTrustAllCertificatesCheckbox.isChecked()); assertTrue(mTrustAllCertificatesCheckbox.getVisibility() == View.VISIBLE); + + // A simple test of an incomplete account, which will fail validation + account.mHostAuthRecv.mPassword = ""; + loadResult = mActivity.mFragment.loadSettings(account); + assertFalse(loadResult); } /**