diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6336f5df4..e761459db 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -99,12 +99,9 @@ > - diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java index c1dd8aba8..06e14c815 100644 --- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java +++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java @@ -24,6 +24,7 @@ import com.android.email.mail.MessagingException; import com.android.email.mail.Sender; import com.android.email.mail.Store; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.HostAuth; import com.android.email.service.EmailServiceProxy; import android.app.Activity; @@ -49,31 +50,29 @@ import android.util.Log; * * There are also two lightweight error dialogs which are used for notification of terminal * conditions. - * - * TODO support for account setup, including - * - autodiscover - * - forwarding account security info */ public class AccountCheckSettingsFragment extends Fragment { public final static String TAG = "AccountCheckSettingsFragment"; // Debugging flags - for debugging the UI - // If true, walks through a "fake" account check cycle - private static final boolean DEBUG_FAKE_CHECK_CYCLE = false; // DO NOT CHECK IN WHILE TRUE - // If true, fake check cycle runs but returns failure - private static final boolean DEBUG_FAKE_CHECK_ERR = false; // DO NOT CHECK IN WHILE TRUE - // If true, performs real check(s), but always returns fixed OK result - private static final boolean DEBUG_FORCE_RESULT_OK = false; // DO NOT CHECK IN WHILE TRUE + // If true, use a "fake" account check cycle + private static final boolean DEBUG_FAKE_CHECK_CYCLE = false; // DO NOT CHECK IN TRUE + // If true, use fake check cycle, return failure + private static final boolean DEBUG_FAKE_CHECK_ERR = false; // DO NOT CHECK IN TRUE + // If true, use fake check cycle, return "security required" + private static final boolean DEBUG_FORCE_SECURITY_REQUIRED = false; // DO NOT CHECK IN TRUE // State private final static int STATE_START = 0; private final static int STATE_CHECK_AUTODISCOVER = 1; private final static int STATE_CHECK_INCOMING = 2; private final static int STATE_CHECK_OUTGOING = 3; - private final static int STATE_CHECK_OK = 4; // terminal - private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal - private final static int STATE_CHECK_ERROR = 6; // terminal + private final static int STATE_CHECK_OK = 4; // terminal + private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal + private final static int STATE_CHECK_ERROR = 6; // terminal + private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal + private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal private int mState = STATE_START; // Support for UI @@ -81,22 +80,45 @@ public class AccountCheckSettingsFragment extends Fragment { private CheckingDialog mCheckingDialog; private int mErrorStringId; private String mErrorMessage; - private ErrorDialog mErrorDialog; - private SecurityRequiredDialog mSecurityRequiredDialog; + private HostAuth mAutoDiscoverResult; // Support for AsyncTask and account checking AccountCheckTask mAccountCheckTask; - + + // Result codes returned by onCheckSettingsComplete. + /** Check settings returned successfully */ + public final static int CHECK_SETTINGS_OK = 0; + /** Check settings failed due to connection, authentication, or other server error */ + public final static int CHECK_SETTINGS_SERVER_ERROR = 1; + /** Check settings failed due to user refusing to accept security requirements */ + public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2; + + // Result codes returned by onAutoDiscoverComplete. + /** AutoDiscover completed successfully with server setup data */ + public final static int AUTODISCOVER_OK = 0; + /** AutoDiscover completed with no data (no server or AD not supported) */ + public final static int AUTODISCOVER_NO_DATA = 1; + /** AutoDiscover reported authentication error */ + public final static int AUTODISCOVER_AUTHENTICATION = 2; + /** * Callback interface for any target or activity doing account check settings */ public interface Callbacks { /** - * CheckSettings completed with no errors + * Called when CheckSettings completed + * @param result check settings result code - success is CHECK_SETTINGS_OK */ - public void onCheckSettingsOk(); + public void onCheckSettingsComplete(int result); + + /** + * Called when autodiscovery completes. + * @param result autodiscovery result code - success is AUTODISCOVER_OK + * @param hostAuth configuration data returned by AD server, or null if no data available + */ + public void onAutoDiscoverComplete(int result, HostAuth hostAuth); } - + /** * Create a retained, invisible fragment that checks accounts * @@ -133,17 +155,14 @@ public class AccountCheckSettingsFragment extends Fragment { if (mAccountCheckTask == null) { int checkMode = getTargetRequestCode(); Account checkAccount = SetupData.getAccount(); - String storeUri = checkAccount.getStoreUri(getActivity()); - String storeHostname = checkAccount.mHostAuthRecv.mAddress; - String senderUri = checkAccount.getSenderUri(getActivity()); mAccountCheckTask = (AccountCheckTask) - new AccountCheckTask(checkMode, storeUri, storeHostname, senderUri) + new AccountCheckTask(checkMode, checkAccount) .execute(); } // if reattaching, update progress/error UI by re-reporting the previous values if (mState != STATE_START) { - reportProgress(mState, mErrorStringId, mErrorMessage); + reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult); } } @@ -175,48 +194,73 @@ public class AccountCheckSettingsFragment extends Fragment { * @param newState The new progress state being reported * @param errorStringId Resource Id of an error string to display * @param errorMessage Additional string to insert if the resource string takes a parameter. + * @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server */ - public void reportProgress(int newState, int errorStringId, String errorMessage) { + public void reportProgress(int newState, int errorStringId, String errorMessage, + HostAuth autoDiscoverResult) { mState = newState; mErrorStringId = errorStringId; mErrorMessage = errorMessage; + mAutoDiscoverResult = autoDiscoverResult; // If we are attached, create, recover, and/or update the dialog if (mAttached) { FragmentManager fm = getFragmentManager(); - if (newState == STATE_CHECK_OK) { - // immediately terminate, clean up, and report back - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. exit self - fm.popBackStack(); - // 3. report OK back to target fragment - Callbacks callbackTarget = getCallbackTarget(); - callbackTarget.onCheckSettingsOk(); - } else if (newState == STATE_CHECK_SHOW_SECURITY) { - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. launch the error dialog - mSecurityRequiredDialog = SecurityRequiredDialog.newInstance(this, mErrorMessage); - fm.openTransaction().add(mSecurityRequiredDialog, SecurityRequiredDialog.TAG) - .commit(); - } else if (newState == STATE_CHECK_ERROR) { - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. launch the error dialog - mErrorDialog = ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage); - fm.openTransaction().add(mErrorDialog, ErrorDialog.TAG).commit(); - } else { - // Display a normal progress message - mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG); + switch (newState) { + case STATE_CHECK_OK: + // immediately terminate, clean up, and report back + // 1. get rid of progress dialog (if any) + recoverAndDismissCheckingDialog(); + // 2. exit self + fm.popBackStack(); + // 3. report OK back to target fragment or activity + getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK); + break; + case STATE_CHECK_SHOW_SECURITY: + // 1. get rid of progress dialog (if any) + recoverAndDismissCheckingDialog(); + // 2. launch the error dialog + SecurityRequiredDialog securityRequiredDialog = + SecurityRequiredDialog.newInstance(this, mErrorMessage); + fm.openTransaction() + .add(securityRequiredDialog, SecurityRequiredDialog.TAG) + .commit(); + break; + case STATE_CHECK_ERROR: + case STATE_AUTODISCOVER_AUTH_DIALOG: + // 1. get rid of progress dialog (if any) + recoverAndDismissCheckingDialog(); + // 2. launch the error dialog + ErrorDialog errorDialog = + ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage); + fm.openTransaction() + .add(errorDialog, ErrorDialog.TAG) + .commit(); + break; + case STATE_AUTODISCOVER_RESULT: + // 1. get rid of progress dialog (if any) + recoverAndDismissCheckingDialog(); + // 2. exit self + fm.popBackStack(); + // 3. report back to target fragment or activity + getCallbackTarget().onAutoDiscoverComplete( + (mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA, + mAutoDiscoverResult); + break; + default: + // Display a normal progress message + mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG); - if (mCheckingDialog == null) { - mCheckingDialog = CheckingDialog.newInstance(this, mState); - fm.openTransaction().add(mCheckingDialog, CheckingDialog.TAG).commit(); - } else { - mCheckingDialog.updateProgress(mState); - } + if (mCheckingDialog == null) { + mCheckingDialog = CheckingDialog.newInstance(this, mState); + fm.openTransaction() + .add(mCheckingDialog, CheckingDialog.TAG) + .commit(); + } else { + mCheckingDialog.updateProgress(mState); + } + break; } } } @@ -234,7 +278,7 @@ public class AccountCheckSettingsFragment extends Fragment { return (Callbacks) activity; } throw new IllegalStateException(); - } + } /** * Recover and dismiss the progress dialog fragment @@ -266,10 +310,18 @@ public class AccountCheckSettingsFragment extends Fragment { /** * This is called when the user clicks "edit" from the error dialog. The error dialog * should have already dismissed itself. - * This should cause us to remain in the current screen (not accepting the settings) + * Depending on the context, the target will remain in the current activity (e.g. editing + * settings) or return to its own parent (e.g. enter new credentials). */ private void onErrorDialogEditButton() { - // Exit self with no report - this is "cancel" + Callbacks callbackTarget = getCallbackTarget(); + if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) { + // report auth error to target fragment or activity + callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null); + } else { + // report check settings failure to target fragment or activity + callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR); + } getFragmentManager().popBackStack(); } @@ -277,16 +329,37 @@ public class AccountCheckSettingsFragment extends Fragment { * This is called when the user clicks "ok" or "cancel" on the "security required" dialog. * Shuts everything down and dismisses everything, and reports the result appropriately. */ - private void onSecurityRequiredDialogButtonOk(boolean okPressed) { - // 1. handle OK - notify that security is OK and we can proceed - if (okPressed) { - Callbacks callbackTarget = getCallbackTarget(); - callbackTarget.onCheckSettingsOk(); - } + private void onSecurityRequiredDialogResultOk(boolean okPressed) { + // 1. handle OK/cancel - notify that security is OK and we can proceed + Callbacks callbackTarget = getCallbackTarget(); + callbackTarget.onCheckSettingsComplete( + okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY); + // 2. kill self getFragmentManager().popBackStack(); } + /** + * This exception class is used to report autodiscover results via the reporting mechanism. + */ + public static class AutoDiscoverResults extends MessagingException { + public final HostAuth mHostAuth; + + /** + * @param authenticationError true if auth failure, false for result (or no response) + * @param hostAuth null for "no autodiscover", non-null for server info to return + */ + public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) { + super(null); + if (authenticationError) { + mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED; + } else { + mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT; + } + mHostAuth = hostAuth; + } + } + /** * This AsyncTask does the actual account checking * @@ -300,17 +373,22 @@ public class AccountCheckSettingsFragment extends Fragment { final String mStoreUri; final String mStoreHost; final String mSenderUri; + final String mCheckEmail; + final String mCheckPassword; /** * Create task and parameterize it * @param mode bits request operations + * @param checkAccount account holding values to be checked */ - public AccountCheckTask(int mode, String storeUri, String storeHost, String senderUri) { + public AccountCheckTask(int mode, Account checkAccount) { mContext = getActivity().getApplicationContext(); mMode = mode; - mStoreUri = storeUri; - mStoreHost = storeHost; - mSenderUri = senderUri; + mStoreUri = checkAccount.getStoreUri(mContext); + mStoreHost = checkAccount.mHostAuthRecv.mAddress; + mSenderUri = checkAccount.getSenderUri(mContext); + mCheckEmail = checkAccount.mEmailAddress; + mCheckPassword = checkAccount.mHostAuthRecv.mPassword; } @Override @@ -320,11 +398,31 @@ public class AccountCheckSettingsFragment extends Fragment { } try { - // TODO: AutoDiscover if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { if (isCancelled()) return null; publishProgress(STATE_CHECK_AUTODISCOVER); - return new MessagingException(-1, "Autodiscover unimplemented"); + Log.d(Email.LOG_TAG, "Begin auto-discover for " + mCheckEmail); + Store store = Store.getInstance(mStoreUri, mContext, null); + Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword); + // Result will be one of: + // null: remote exception - proceed to manual setup + // MessagingException.AUTHENTICATION_FAILED: username/password rejected + // Other error: proceed to manual setup + // No error: return autodiscover results + if (result == null) { + return new AutoDiscoverResults(false, null); + } + int errorCode = + result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE); + if (errorCode == MessagingException.AUTHENTICATION_FAILED) { + return new AutoDiscoverResults(true, null); + } else if (errorCode != MessagingException.NO_ERROR) { + return new AutoDiscoverResults(false, null); + } else { + HostAuth serverInfo = (HostAuth) + result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH); + return new AutoDiscoverResults(false, serverInfo); + } } // Check Incoming Settings @@ -373,7 +471,7 @@ public class AccountCheckSettingsFragment extends Fragment { } /** - * Dummy background worker, for testing UI only. STOPSHIP remove this + * Dummy background worker, for testing UI only. */ private MessagingException fakeChecker() { // Dummy: Publish a series of progress setups, 2 sec delays between them; @@ -382,32 +480,39 @@ public class AccountCheckSettingsFragment extends Fragment { if (isCancelled()) return null; if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { publishProgress(STATE_CHECK_AUTODISCOVER); - if (DEBUG_FAKE_CHECK_ERR) { - return new MessagingException(MessagingException.AUTHENTICATION_FAILED); - } try { Thread.sleep(DELAY); } catch (InterruptedException e) { } + if (DEBUG_FAKE_CHECK_ERR) { + return new MessagingException(MessagingException.AUTHENTICATION_FAILED); + } + // Return "real" AD results + HostAuth serverInfo = new HostAuth(); + serverInfo.setStoreUri("eas://user:password@testserver.com"); + return new AutoDiscoverResults(false, serverInfo); } if (isCancelled()) return null; if ((mMode & SetupData.CHECK_INCOMING) != 0) { publishProgress(STATE_CHECK_INCOMING); - if (DEBUG_FAKE_CHECK_ERR) { - return new MessagingException(MessagingException.IOERROR); - } try { Thread.sleep(DELAY); } catch (InterruptedException e) { } + if (DEBUG_FAKE_CHECK_ERR) { + return new MessagingException(MessagingException.IOERROR); + } else if (DEBUG_FORCE_SECURITY_REQUIRED) { + return new MessagingException( + MessagingException.SECURITY_POLICIES_REQUIRED); + } } if (isCancelled()) return null; if ((mMode & SetupData.CHECK_OUTGOING) != 0) { publishProgress(STATE_CHECK_OUTGOING); - if (DEBUG_FAKE_CHECK_ERR) { - return new MessagingException(MessagingException.TLS_REQUIRED); - } try { Thread.sleep(DELAY); } catch (InterruptedException e) { } + if (DEBUG_FAKE_CHECK_ERR) { + return new MessagingException(MessagingException.TLS_REQUIRED); + } } return null; } @@ -419,36 +524,58 @@ public class AccountCheckSettingsFragment extends Fragment { @Override protected void onProgressUpdate(Integer... progress) { if (isCancelled()) return; - reportProgress(progress[0], 0, null); + reportProgress(progress[0], 0, null, null); } /** - * Result handler (runs in UI thread) + * Result handler (runs in UI thread). + * + * AutoDiscover authentication errors are handled a bit differently than the + * other errors; If encountered, we display the error dialog, but we return with + * a different callback used only for AutoDiscover. + * * @param result null for a successful check; exception for various errors */ @Override protected void onPostExecute(MessagingException result) { if (isCancelled()) return; if (result == null) { - reportProgress(STATE_CHECK_OK, 0, null); + reportProgress(STATE_CHECK_OK, 0, null, null); } else { + int progressState = STATE_CHECK_ERROR; int exceptionType = result.getExceptionType(); String message = result.getMessage(); - int id; + HostAuth hostAuth = null; + int id = 0; + switch (exceptionType) { + // NOTE: AutoDiscover reports have their own reporting state, handle differently + // from the other exception types + case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: + id = (message == null) + ? R.string.account_setup_failed_dlg_auth_message + : R.string.account_setup_failed_dlg_auth_message_fmt; + progressState = STATE_AUTODISCOVER_AUTH_DIALOG; + break; + case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT: + hostAuth = ((AutoDiscoverResults)result).mHostAuth; + progressState = STATE_AUTODISCOVER_RESULT; + break; + // NOTE: Security policies required has its own report state, handle it a bit // differently from the other exception types. case MessagingException.SECURITY_POLICIES_REQUIRED: - reportProgress(STATE_CHECK_SHOW_SECURITY, 0, message); - return; - // Remaining exception types are handled together, move us to STATE_CHECK_ERROR + progressState = STATE_CHECK_SHOW_SECURITY; + break; + + // The remaining exception types are handled by setting the state to + // STATE_CHECK_ERROR (above, default) and conversion to specific error strings. case MessagingException.CERTIFICATE_VALIDATION_ERROR: id = (message == null) ? R.string.account_setup_failed_dlg_certificate_message : R.string.account_setup_failed_dlg_certificate_message_fmt; break; case MessagingException.AUTHENTICATION_FAILED: - case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: id = (message == null) ? R.string.account_setup_failed_dlg_auth_message : R.string.account_setup_failed_dlg_auth_message_fmt; @@ -477,7 +604,7 @@ public class AccountCheckSettingsFragment extends Fragment { : R.string.account_setup_failed_dlg_server_message_fmt; break; } - reportProgress(STATE_CHECK_ERROR, id, message); + reportProgress(progressState, id, message, hostAuth); } } } @@ -486,8 +613,6 @@ public class AccountCheckSettingsFragment extends Fragment { * Simple dialog that shows progress as we work through the settings checks. * This is stateless except for its UI (e.g. current strings) and can be torn down or * recreated at any time without affecting the account checking progress. - * - * TODO: Indeterminate progress or other icon */ public static class CheckingDialog extends DialogFragment { public final static String TAG = "CheckProgressDialog"; @@ -620,7 +745,14 @@ public class AccountCheckSettingsFragment extends Fragment { } /** - * The "security required" error dialog. Calls back to onSecurityRequiredDialogButtonOk(). + * The "security required" error dialog. This is presented whenever an exchange account + * reports that it will require security policy control, and provide the user with the + * opportunity to accept or deny this. + * + * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back + * to the target as if the settings check was "ok". If the user clicks "cancel", calls + * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the + * same as any other failed check.) */ public static class SecurityRequiredDialog extends DialogFragment { public final static String TAG = "SecurityRequiredDialog"; @@ -657,7 +789,7 @@ public class AccountCheckSettingsFragment extends Fragment { new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dismiss(); - target.onSecurityRequiredDialogButtonOk(true); + target.onSecurityRequiredDialogResultOk(true); } }) .setNegativeButton( @@ -665,7 +797,7 @@ public class AccountCheckSettingsFragment extends Fragment { new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dismiss(); - target.onSecurityRequiredDialogButtonOk(false); + target.onSecurityRequiredDialogResultOk(false); } }) .create(); diff --git a/src/com/android/email/activity/setup/AccountServerBaseFragment.java b/src/com/android/email/activity/setup/AccountServerBaseFragment.java index 0bfbfbcb1..662d73bd8 100644 --- a/src/com/android/email/activity/setup/AccountServerBaseFragment.java +++ b/src/com/android/email/activity/setup/AccountServerBaseFragment.java @@ -17,6 +17,7 @@ package com.android.email.activity.setup; import com.android.email.R; +import com.android.email.provider.EmailContent.HostAuth; import android.app.Activity; import android.app.Fragment; @@ -59,18 +60,19 @@ public abstract class AccountServerBaseFragment extends Fragment public void onProceedNext(int checkMode, AccountServerBaseFragment target); /** - * Called when account checker returns "ok". Fragments are responsible for saving + * Called when account checker completes. Fragments are responsible for saving * own edited data; This is primarily for the activity to do post-check navigation. + * @param result check settings result code - success is CHECK_SETTINGS_OK * @param setupMode signals if we were editing or creating */ - public void onCheckSettingsOk(int setupMode); + public void onCheckSettingsComplete(int result, int setupMode); } private static class EmptyCallback implements Callback { public static final Callback INSTANCE = new EmptyCallback(); @Override public void onEnableProceedButtons(boolean enable) { } @Override public void onProceedNext(int checkMode, AccountServerBaseFragment target) { } - @Override public void onCheckSettingsOk(int setupMode) { } + @Override public void onCheckSettingsComplete(int result, int setupMode) { } } /** @@ -157,17 +159,28 @@ public abstract class AccountServerBaseFragment extends Fragment /** * Implements AccountCheckSettingsFragment.Callbacks * - * Handle OK result from check settings. Save settings, and exit to previous fragment. + * Handle OK or error result from check settings. Save settings, and exit to previous fragment. */ @Override - public void onCheckSettingsOk() { - if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT) { - saveSettingsAfterEdit(); - } else { - saveSettingsAfterSetup(); + public void onCheckSettingsComplete(int result) { + if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT) { + saveSettingsAfterEdit(); + } else { + saveSettingsAfterSetup(); + } } - // Signal to owning activity that a settings check was OK - mCallback.onCheckSettingsOk(SetupData.getFlowMode()); + // Signal to owning activity that a settings check completed + mCallback.onCheckSettingsComplete(result, SetupData.getFlowMode()); + } + + /** + * Implements AccountCheckSettingsFragment.Callbacks + * This is overridden only by AccountSetupExchange + */ + @Override + public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { + throw new IllegalStateException(); } /** diff --git a/src/com/android/email/activity/setup/AccountSettingsXL.java b/src/com/android/email/activity/setup/AccountSettingsXL.java index 4bd9bab5c..9cde11a09 100644 --- a/src/com/android/email/activity/setup/AccountSettingsXL.java +++ b/src/com/android/email/activity/setup/AccountSettingsXL.java @@ -493,8 +493,10 @@ public class AccountSettingsXL extends PreferenceActivity implements OnClickList * After verifying a new server configuration as OK, we return here and continue. This * simply does a "back" to exit the settings screen. */ - public void onCheckSettingsOk(int setupMode) { - onBackPressed(); + public void onCheckSettingsComplete(int result, int setupMode) { + if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + onBackPressed(); + } } } diff --git a/src/com/android/email/activity/setup/AccountSetupBasics.java b/src/com/android/email/activity/setup/AccountSetupBasics.java index 726c68a77..b420e3e01 100644 --- a/src/com/android/email/activity/setup/AccountSetupBasics.java +++ b/src/com/android/email/activity/setup/AccountSetupBasics.java @@ -21,6 +21,7 @@ import com.android.email.Utility; import com.android.email.VendorPolicyLoader; import com.android.email.activity.Welcome; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.HostAuth; import android.app.Activity; import android.app.FragmentTransaction; @@ -52,12 +53,6 @@ public class AccountSetupBasics extends AccountSetupActivity fromActivity.startActivity(new Intent(fromActivity, AccountSetupBasics.class)); } - public static void actionNewAccountWithCredentials(Activity fromActivity, - String username, String password, int accountFlowMode) { - SetupData.init(accountFlowMode, username, password); - fromActivity.startActivity(new Intent(fromActivity, AccountSetupBasics.class)); - } - /** * This generates setup data that can be used to start a self-contained account creation flow * for exchange accounts. @@ -149,9 +144,20 @@ public class AccountSetupBasics extends AccountSetupActivity * skipping here). */ @Override - public void onCheckSettingsOk() { - AccountSetupOptions.actionOptions(this); - finish(); + public void onCheckSettingsComplete(int result) { + if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + AccountSetupOptions.actionOptions(this); + finish(); + } + } + + /** + * Implements AccountCheckSettingsFragment.Callbacks + * This is overridden only by AccountSetupExchange + */ + @Override + public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { + throw new IllegalStateException(); } /** @@ -238,7 +244,8 @@ public class AccountSetupBasics extends AccountSetupActivity * Implements AccountSetupBasicsFragment.Callback */ @Override - public void onProceedManual() { + public void onProceedManual(boolean allowAutoDiscover) { + SetupData.setAllowAutodiscover(allowAutoDiscover); AccountSetupAccountType.actionSelectAccountType(this); } } diff --git a/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java b/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java index 4a5030e24..8f4075713 100644 --- a/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java @@ -86,7 +86,7 @@ public class AccountSetupBasicsFragment extends Fragment implements TextWatcher public interface Callback { public void onEnableProceedButtons(boolean enable); public void onProceedAutomatic(); - public void onProceedManual(); + public void onProceedManual(boolean allowAutoDiscover); public void onProceedDebugSettings(); } @@ -94,7 +94,7 @@ public class AccountSetupBasicsFragment extends Fragment implements TextWatcher public static final Callback INSTANCE = new EmptyCallback(); @Override public void onEnableProceedButtons(boolean enable) { } @Override public void onProceedAutomatic() { } - @Override public void onProceedManual() { } + @Override public void onProceedManual(boolean allowAutoDiscover) { } @Override public void onProceedDebugSettings() { } } @@ -441,7 +441,7 @@ public class AccountSetupBasicsFragment extends Fragment implements TextWatcher } account.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); - mCallback.onProceedManual(); + mCallback.onProceedManual(allowAutoDiscover); } /** diff --git a/src/com/android/email/activity/setup/AccountSetupExchange.java b/src/com/android/email/activity/setup/AccountSetupExchange.java index 957359ba8..62ad75ffe 100644 --- a/src/com/android/email/activity/setup/AccountSetupExchange.java +++ b/src/com/android/email/activity/setup/AccountSetupExchange.java @@ -17,15 +17,13 @@ package com.android.email.activity.setup; import com.android.email.R; -import com.android.email.SecurityPolicy.PolicySet; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.HostAuth; -import com.android.email.service.EmailServiceProxy; import android.app.Activity; +import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; /** * Provides generic setup for Exchange accounts. The following fields are supported: @@ -60,31 +58,30 @@ import android.os.Parcelable; * Write new values (save to provider) * Proceed to options screen * finish() (removes self from back stack) - * - * 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 AccountSetupExchangeFragment.Callback { + // Keys for savedInstanceState + private final static String STATE_STARTED_AUTODISCOVERY = + "AccountSetupExchange.StartedAutoDiscovery"; + + boolean mStartedAutoDiscovery; /* package */ AccountSetupExchangeFragment mFragment; /* package */ boolean mNextButtonEnabled; - public static void actionIncomingSettings(Activity fromActivity, int mode, Account acct) { - SetupData.init(mode, acct); + public static void actionIncomingSettings(Activity fromActivity, int mode, Account account) { + SetupData.setFlowMode(mode); + SetupData.setAccount(account); fromActivity.startActivity(new Intent(fromActivity, AccountSetupExchange.class)); } + // TODO this is vestigial, remove it public static void actionEditIncomingSettings(Activity fromActivity, int mode, Account acct) { actionIncomingSettings(fromActivity, mode, acct); } - /** - * For now, we'll simply replicate outgoing, for the purpose of satisfying the - * account settings flow. - */ + // TODO this is vestigial, remove it public static void actionEditOutgoingSettings(Activity fromActivity, int mode, Account acct) { actionIncomingSettings(fromActivity, mode, acct); } @@ -98,11 +95,24 @@ public class AccountSetupExchange extends AccountSetupActivity getFragmentManager().findFragmentById(R.id.setup_fragment); mFragment.setCallback(this); - startAutoDiscover(); + // One-shot to launch autodiscovery at the entry to this activity (but not if it restarts) + mStartedAutoDiscovery = false; + if (savedInstanceState != null) { + mStartedAutoDiscovery = savedInstanceState.getBoolean(STATE_STARTED_AUTODISCOVERY); + } + if (!mStartedAutoDiscovery) { + startAutoDiscover(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(STATE_STARTED_AUTODISCOVERY, mStartedAutoDiscovery); } /** - * If the conditions are right, launch the autodiscover activity. If it succeeds (even + * If the conditions are right, launch the autodiscover fragment. If it succeeds (even * partially) it will prefill the setup fields and we can proceed as if the user entered them. * * Conditions for skipping: @@ -111,6 +121,10 @@ public class AccountSetupExchange extends AccountSetupActivity * Username or password not entered yet */ private void startAutoDiscover() { + // Note that we've started autodiscovery - even if we decide not to do it, + // this prevents repeating. + mStartedAutoDiscovery = true; + if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT || !SetupData.isAllowAutodiscover()) { return; @@ -121,81 +135,28 @@ public class AccountSetupExchange extends AccountSetupActivity String username = account.mHostAuthRecv.mLogin; String password = account.mHostAuthRecv.mPassword; if (username != null && password != null) { - AccountSetupCheckSettings.actionAutoDiscover(this, account.mEmailAddress, password); + onProceedNext(SetupData.CHECK_AUTODISCOVER, mFragment); } } /** - * There are three cases handled here, so we split out into separate sections. - * 1. Validate existing account (edit) - * 2. Validate new account - * 3. Autodiscover for new account + * Implements AccountCheckSettingsFragment.Callbacks * - * For each case, there are two or more paths for success or failure. + * @param result configuration data returned by AD server, or null if no data available */ - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == AccountSetupCheckSettings.REQUEST_CODE_VALIDATE) { - if (SetupData.getFlowMode() == SetupData.FLOW_MODE_EDIT) { - doActivityResultValidateExistingAccount(resultCode, data); - } else { - doActivityResultValidateNewAccount(resultCode, data); - } - } else if (requestCode == AccountSetupCheckSettings.REQUEST_CODE_AUTO_DISCOVER) { - doActivityResultAutoDiscoverNewAccount(resultCode, data); - } - } - - /** - * 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) { - if (resultCode == RESULT_OK) { - mFragment.saveSettingsAfterEdit(); - finish(); - } - } - - /** - * Process activity result when validating new account - */ - private void doActivityResultValidateNewAccount(int resultCode, Intent data) { - if (resultCode == RESULT_OK) { - // Go directly to next screen - PolicySet ps = null; - if ((data != null) && data.hasExtra(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET)) { - ps = (PolicySet)data.getParcelableExtra( - EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET); - } - AccountSetupOptions.actionOptions(this); - finish(); - } else if (resultCode == AccountSetupCheckSettings.RESULT_SECURITY_REQUIRED_USER_CANCEL) { - finish(); - } - // else (resultCode not OK) - just return into this activity for further editing - } - - /** - * Process activity result when provisioning new account via autodiscovery - */ - private void doActivityResultAutoDiscoverNewAccount(int resultCode, Intent data) { + public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { // If authentication failed, exit immediately (to re-enter credentials) - if (resultCode == AccountSetupCheckSettings.RESULT_AUTO_DISCOVER_AUTH_FAILED) { + if (result == AccountCheckSettingsFragment.AUTODISCOVER_AUTHENTICATION) { finish(); return; } // If data was returned, populate the account & populate the UI fields and validate it - if (data != null) { - Parcelable p = data.getParcelableExtra("HostAuth"); - if (p != null) { - HostAuth hostAuth = (HostAuth)p; - boolean valid = mFragment.setHostAuthFromAutodiscover(hostAuth); - if (valid) { - // "click" next to launch server verification - mFragment.onNext(); - } + if (result == AccountCheckSettingsFragment.AUTODISCOVER_OK) { + boolean valid = mFragment.setHostAuthFromAutodiscover(hostAuth); + if (valid) { + // "click" next to launch server verification + mFragment.onNext(); } } // Otherwise, proceed into this activity for manual setup @@ -205,7 +166,12 @@ public class AccountSetupExchange extends AccountSetupActivity * Implements AccountServerBaseFragment.Callback */ public void onProceedNext(int checkMode, AccountServerBaseFragment target) { - AccountSetupCheckSettings.actionCheckSettings(this, checkMode); + AccountCheckSettingsFragment checkerFragment = + AccountCheckSettingsFragment.newInstance(checkMode, target); + FragmentTransaction transaction = getFragmentManager().openTransaction(); + transaction.replace(R.id.setup_fragment, checkerFragment); + transaction.addToBackStack("back"); + transaction.commit(); } /** @@ -222,10 +188,25 @@ public class AccountSetupExchange extends AccountSetupActivity /** * Implements AccountServerBaseFragment.Callback - * TODO - should never happen, we don't use checksettings fragment (yet) for account setup + * + * If the checked settings are OK, proceed to options screen. If the user rejects security, + * exit this screen. For all other errors, remain here for editing. */ - public void onCheckSettingsOk(int setupMode) { - throw new IllegalStateException(); + public void onCheckSettingsComplete(int result, int setupMode) { + switch (result) { + case AccountCheckSettingsFragment.CHECK_SETTINGS_OK: + if (SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { + AccountSetupOptions.actionOptions(this); + } + finish(); + break; + case AccountCheckSettingsFragment.CHECK_SETTINGS_SECURITY_USER_DENY: + finish(); + break; + default: + case AccountCheckSettingsFragment.CHECK_SETTINGS_SERVER_ERROR: + // Do nothing - remain in this screen + break; + } } - } diff --git a/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java index d7e804a95..d5dd10ff0 100644 --- a/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java @@ -29,7 +29,6 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.RemoteException; -import android.preference.PreferenceActivity; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -314,11 +313,12 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment } /** - * TODO reconcile this generic entry point with setHostAuthFromAutodiscover + * This entry point is not used (unlike in AccountSetupIncomingFragment) because the data + * is already saved by onNext(). + * TODO: Reconcile this, to make them more consistent. */ @Override public void saveSettingsAfterSetup() { - // TODO implement } /** @@ -365,6 +365,15 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment return uri; } + /** + * Implements AccountCheckSettingsFragment.Callbacks + */ + @Override + public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { + AccountSetupExchange activity = (AccountSetupExchange) getActivity(); + activity.onAutoDiscoverComplete(result, hostAuth); + } + /** * Entry point from Activity, when "next" button is clicked */ @@ -394,15 +403,6 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment throw new Error(use); } - // STOPSHIP - use new checker fragment only during account settings (TODO: account setup) - Activity activity = getActivity(); - if (activity instanceof PreferenceActivity) { - AccountCheckSettingsFragment checkerFragment = - AccountCheckSettingsFragment.newInstance(SetupData.CHECK_INCOMING, this); - ((PreferenceActivity)activity).startPreferenceFragment(checkerFragment, true); - } else { - // STOPSHIP remove this old code - mCallback.onProceedNext(SetupData.CHECK_INCOMING, this); - } + mCallback.onProceedNext(SetupData.CHECK_INCOMING, this); } } diff --git a/src/com/android/email/activity/setup/AccountSetupIncoming.java b/src/com/android/email/activity/setup/AccountSetupIncoming.java index 2ca3443e2..8129ebe38 100644 --- a/src/com/android/email/activity/setup/AccountSetupIncoming.java +++ b/src/com/android/email/activity/setup/AccountSetupIncoming.java @@ -90,11 +90,13 @@ public class AccountSetupIncoming extends AccountSetupActivity * * If the checked settings are OK, proceed to outgoing settings screen */ - public void onCheckSettingsOk(int setupMode) { - if (SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { - AccountSetupOutgoing.actionOutgoingSettings(this, SetupData.getFlowMode(), - SetupData.getAccount()); - finish(); + public void onCheckSettingsComplete(int result, int setupMode) { + if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + if (SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { + AccountSetupOutgoing.actionOutgoingSettings(this, SetupData.getFlowMode(), + SetupData.getAccount()); + finish(); + } } } } diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoing.java b/src/com/android/email/activity/setup/AccountSetupOutgoing.java index 82d1249b7..1bf42c847 100644 --- a/src/com/android/email/activity/setup/AccountSetupOutgoing.java +++ b/src/com/android/email/activity/setup/AccountSetupOutgoing.java @@ -88,10 +88,12 @@ public class AccountSetupOutgoing extends Activity * * If the checked settings are OK, proceed to options screen */ - public void onCheckSettingsOk(int setupMode) { - if (SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { - AccountSetupOptions.actionOptions(this); + public void onCheckSettingsComplete(int result, int setupMode) { + if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + if (SetupData.getFlowMode() != SetupData.FLOW_MODE_EDIT) { + AccountSetupOptions.actionOptions(this); + } + finish(); } - finish(); } } diff --git a/src/com/android/email/activity/setup/SetupData.java b/src/com/android/email/activity/setup/SetupData.java index dfccfc349..c1eb7eea1 100644 --- a/src/com/android/email/activity/setup/SetupData.java +++ b/src/com/android/email/activity/setup/SetupData.java @@ -156,15 +156,6 @@ public class SetupData implements Parcelable { data.mFlowMode = flowMode; } - public static void init(int flowMode, String username, String password) { - SetupData data = getInstance(); - data.commonInit(); - data.mFlowMode = flowMode; - data.mUsername = username; - data.mPassword = password; - data.commonInit(); - } - public static void init(int flowMode, Account account) { SetupData data = getInstance(); data.commonInit(); diff --git a/src/com/android/email/mail/MessagingException.java b/src/com/android/email/mail/MessagingException.java index 981d01148..4cd120531 100644 --- a/src/com/android/email/mail/MessagingException.java +++ b/src/com/android/email/mail/MessagingException.java @@ -57,6 +57,8 @@ public class MessagingException extends Exception { public static final int CERTIFICATE_VALIDATION_ERROR = 10; /** Authentication failed during autodiscover */ public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11; + /** Autodiscover completed with a result (non-error) */ + public static final int AUTODISCOVER_AUTHENTICATION_RESULT = 12; protected int mExceptionType;