Convert exchange setup to use checker fragment

* AccountCheckSettingsFragment now supports AutoDiscover
* Clean up callbacks for account check & autodiscover errors
* AccountSetupExchange now supports rotation
* Remove dead code in SetupData & AccountSetupBasics

TODO next CL: Remove all edit flows from setup activities
TODO next CL: Remove old CheckSettings activity

Change-Id: I3c9884856ca6e70226374fdb28400bfb3588387e
This commit is contained in:
Andrew Stadler 2010-09-13 14:04:45 -07:00
parent d4aac1e847
commit 2731aef45c
12 changed files with 368 additions and 239 deletions

View File

@ -99,12 +99,9 @@
>
</activity>
<!--EXCHANGE-REMOVE-SECTION-START-->
<!-- This activity ignores configuration changes (e.g. rotation) so that it will
not make multiple calls to AccountSetupCheckSettings. -->
<activity
android:name=".activity.setup.AccountSetupExchange"
android:label="@string/account_setup_exchange_title"
android:configChanges="keyboardHidden|orientation"
>
</activity>
<!--EXCHANGE-REMOVE-SECTION-END-->

View File

@ -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();

View File

@ -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();
}
/**

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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;