diff options
Diffstat (limited to 'src/com/android')
35 files changed, 2464 insertions, 2873 deletions
diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java index 233a3b688..9918641e2 100644 --- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java +++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java @@ -17,17 +17,11 @@ package com.android.email.activity.setup; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; -import android.app.ProgressDialog; import android.content.Context; -import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; -import android.text.TextUtils; import com.android.email.R; import com.android.email.mail.Sender; @@ -69,28 +63,21 @@ public class AccountCheckSettingsFragment extends Fragment { private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal private int mState = STATE_START; - private SetupDataFragment mSetupData; + + // Args + private final static String ARGS_MODE = "mode"; + + private int mMode; // Support for UI private boolean mAttached; private boolean mPaused = false; - private CheckingDialog mCheckingDialog; private MessagingException mProgressException; // 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; - /** Check settings failed due to certificate being required - user needs to pick immediately. */ - public final static int CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED = 3; - - // Result codes returned by onAutoDiscoverComplete. + // Result codes returned by onCheckSettingsAutoDiscoverComplete. /** 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) */ @@ -101,18 +88,32 @@ public class AccountCheckSettingsFragment extends Fragment { /** * Callback interface for any target or activity doing account check settings */ - public interface Callbacks { + public interface Callback { /** * Called when CheckSettings completed - * @param result check settings result code - success is CHECK_SETTINGS_OK */ - public void onCheckSettingsComplete(int result, SetupDataFragment setupData); + void onCheckSettingsComplete(); + + /** + * Called when we determine that a security policy will need to be installed + * @param hostName Passed back from the MessagingException + */ + void onCheckSettingsSecurityRequired(String hostName); + + /** + * Called when we receive an error while validating the account + * @param reason from + * {@link CheckSettingsErrorDialogFragment#getReasonFromException(MessagingException)} + * @param message from + * {@link CheckSettingsErrorDialogFragment#getErrorString(Context, MessagingException)} + */ + void onCheckSettingsError(int reason, String message); /** * Called when autodiscovery completes. * @param result autodiscovery result code - success is AUTODISCOVER_OK */ - public void onAutoDiscoverComplete(int result, SetupDataFragment setupData); + void onCheckSettingsAutoDiscoverComplete(int result); } // Public no-args constructor needed for fragment re-instantiation @@ -123,9 +124,11 @@ public class AccountCheckSettingsFragment extends Fragment { * * @param mode incoming or outgoing */ - public static AccountCheckSettingsFragment newInstance(int mode, Fragment parentFragment) { + public static AccountCheckSettingsFragment newInstance(int mode) { final AccountCheckSettingsFragment f = new AccountCheckSettingsFragment(); - f.setTargetFragment(parentFragment, mode); + final Bundle b = new Bundle(1); + b.putInt(ARGS_MODE, mode); + f.setArguments(b); return f; } @@ -137,6 +140,7 @@ public class AccountCheckSettingsFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); + mMode = getArguments().getInt(ARGS_MODE); } /** @@ -152,13 +156,12 @@ public class AccountCheckSettingsFragment extends Fragment { // If this is the first time, start the AsyncTask if (mAccountCheckTask == null) { - final int checkMode = getTargetRequestCode(); final SetupDataFragment.SetupDataContainer container = (SetupDataFragment.SetupDataContainer) getActivity(); - mSetupData = container.getSetupData(); - final Account checkAccount = mSetupData.getAccount(); + // TODO: don't pass in the whole SetupDataFragment mAccountCheckTask = (AccountCheckTask) - new AccountCheckTask(checkMode, checkAccount) + new AccountCheckTask(getActivity().getApplicationContext(), this, mMode, + container.getSetupData()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } @@ -193,12 +196,6 @@ public class AccountCheckSettingsFragment extends Fragment { Utility.cancelTaskInterrupt(mAccountCheckTask); mAccountCheckTask = null; } - // Make doubly sure that the dialog isn't pointing at us before we're removed from the - // fragment manager - final Fragment f = getFragmentManager().findFragmentByTag(CheckingDialog.TAG); - if (f != null) { - f.setTargetFragment(null, 0); - } } /** @@ -228,64 +225,39 @@ public class AccountCheckSettingsFragment extends Fragment { 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, mSetupData); + getCallbackTarget().onCheckSettingsComplete(); break; case STATE_CHECK_SHOW_SECURITY: - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. launch the error dialog, if needed - if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) { - String message = ex.getMessage(); - if (message != null) { - message = message.trim(); - } - SecurityRequiredDialog securityRequiredDialog = - SecurityRequiredDialog.newInstance(this, message); - fm.beginTransaction() - .add(securityRequiredDialog, SecurityRequiredDialog.TAG) - .commit(); + // report that we need to accept a security policy + String hostName = ex.getMessage(); + if (hostName != null) { + hostName = hostName.trim(); } + getCallbackTarget().onCheckSettingsSecurityRequired(hostName); break; case STATE_CHECK_ERROR: case STATE_AUTODISCOVER_AUTH_DIALOG: - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. launch the error dialog, if needed - if (fm.findFragmentByTag(ErrorDialog.TAG) == null) { - ErrorDialog errorDialog = ErrorDialog.newInstance( - getActivity(), this, mProgressException); - fm.beginTransaction() - .add(errorDialog, ErrorDialog.TAG) - .commit(); - } + // report that we had an error + final int reason = + CheckSettingsErrorDialogFragment.getReasonFromException(ex); + final String errorMessage = + CheckSettingsErrorDialogFragment.getErrorString(getActivity(), ex); + getCallbackTarget().onCheckSettingsError(reason, errorMessage); break; case STATE_AUTODISCOVER_RESULT: final HostAuth autoDiscoverResult = ((AutoDiscoverResults) ex).mHostAuth; - // 1. get rid of progress dialog (if any) - recoverAndDismissCheckingDialog(); - // 2. exit self - fm.popBackStack(); - // 3. report back to target fragment or activity - getCallbackTarget().onAutoDiscoverComplete( - (autoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA, - mSetupData); + // report autodiscover results back to target fragment or activity + getCallbackTarget().onCheckSettingsAutoDiscoverComplete( + (autoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA); break; default: // Display a normal progress message - mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG); + CheckSettingsProgressDialogFragment checkingDialog = + (CheckSettingsProgressDialogFragment) + fm.findFragmentByTag(CheckSettingsProgressDialogFragment.TAG); - if (mCheckingDialog == null) { - mCheckingDialog = CheckingDialog.newInstance(this, mState); - fm.beginTransaction() - .add(mCheckingDialog, CheckingDialog.TAG) - .commit(); - } else { - mCheckingDialog.updateProgress(mState); + if (checkingDialog != null) { + checkingDialog.updateProgress(mState); } break; } @@ -295,99 +267,19 @@ public class AccountCheckSettingsFragment extends Fragment { /** * Find the callback target, either a target fragment or the activity */ - private Callbacks getCallbackTarget() { + private Callback getCallbackTarget() { final Fragment target = getTargetFragment(); - if (target instanceof Callbacks) { - return (Callbacks) target; + if (target instanceof Callback) { + return (Callback) target; } Activity activity = getActivity(); - if (activity instanceof Callbacks) { - return (Callbacks) activity; + if (activity instanceof Callback) { + return (Callback) activity; } throw new IllegalStateException(); } /** - * Recover and dismiss the progress dialog fragment - */ - private void recoverAndDismissCheckingDialog() { - if (mCheckingDialog == null) { - mCheckingDialog = (CheckingDialog) - getFragmentManager().findFragmentByTag(CheckingDialog.TAG); - } - if (mCheckingDialog != null) { - // TODO: dismissAllowingStateLoss() can cause the fragment to return later as a zombie - // after the fragment manager restores state, if it happens that this call is executed - // after the state is saved. Figure out a way to clean this up later. b/11435698 - mCheckingDialog.dismissAllowingStateLoss(); - mCheckingDialog = null; - } - } - - /** - * This is called when the user clicks "cancel" on the progress dialog. Shuts everything - * down and dismisses everything. - * This should cause us to remain in the current screen (not accepting the settings) - */ - private void onCheckingDialogCancel() { - // 1. kill the checker - Utility.cancelTaskInterrupt(mAccountCheckTask); - mAccountCheckTask = null; - // 2. kill self with no report - this is "cancel" - finish(); - } - - private void onEditCertificateOk() { - getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED, - mSetupData); - finish(); - } - - /** - * This is called when the user clicks "edit" from the error dialog. The error dialog - * should have already dismissed itself. - * 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() { - // 1. handle "edit" - notify callback that we had a problem with the test - final Callbacks callbackTarget = getCallbackTarget(); - if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) { - // report auth error to target fragment or activity - callbackTarget.onAutoDiscoverComplete(CHECK_SETTINGS_SERVER_ERROR, mSetupData); - } else { - // report check settings failure to target fragment or activity - callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR, mSetupData); - } - finish(); - } - - /** Kill self if not already killed. */ - private void finish() { - final FragmentManager fm = getFragmentManager(); - if (fm != null) { - fm.popBackStack(); - } - } - - /** - * 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 onSecurityRequiredDialogResultOk(boolean okPressed) { - // 1. handle OK/cancel - notify that security is OK and we can proceed - final Callbacks callbackTarget = getCallbackTarget(); - callbackTarget.onCheckSettingsComplete( - okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY, mSetupData); - - // 2. kill self if not already killed by callback - final FragmentManager fm = getFragmentManager(); - if (fm != null) { - fm.popBackStack(); - } - } - - /** * This exception class is used to report autodiscover results via the reporting mechanism. */ public static class AutoDiscoverResults extends MessagingException { @@ -414,10 +306,11 @@ public class AccountCheckSettingsFragment extends Fragment { * TODO: It would be better to remove the UI complete from here (the exception->string * conversions). */ - private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> { - + private static class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> { final Context mContext; + final AccountCheckSettingsFragment mCallback; final int mMode; + final SetupDataFragment mSetupData; final Account mAccount; final String mStoreHost; final String mCheckEmail; @@ -425,16 +318,20 @@ public class AccountCheckSettingsFragment extends Fragment { /** * Create task and parameterize it + * @param context application context object * @param mode bits request operations - * @param checkAccount account holding values to be checked + * @param setupData {@link SetupDataFragment} holding values to be checked */ - public AccountCheckTask(int mode, Account checkAccount) { - mContext = getActivity().getApplicationContext(); + public AccountCheckTask(Context context, AccountCheckSettingsFragment callback, int mode, + SetupDataFragment setupData) { + mContext = context; + mCallback = callback; mMode = mode; - mAccount = checkAccount; - mStoreHost = checkAccount.mHostAuthRecv.mAddress; - mCheckEmail = checkAccount.mEmailAddress; - mCheckPassword = checkAccount.mHostAuthRecv.mPassword; + mSetupData = setupData; + mAccount = setupData.getAccount(); + mStoreHost = mAccount.mHostAuthRecv.mAddress; + mCheckEmail = mAccount.mEmailAddress; + mCheckPassword = mAccount.mHostAuthRecv.mPassword; } @Override @@ -539,7 +436,7 @@ public class AccountCheckSettingsFragment extends Fragment { @Override protected void onProgressUpdate(Integer... progress) { if (isCancelled()) return; - reportProgress(progress[0], null); + mCallback.reportProgress(progress[0], null); } /** @@ -555,7 +452,7 @@ public class AccountCheckSettingsFragment extends Fragment { protected void onPostExecute(MessagingException result) { if (isCancelled()) return; if (result == null) { - reportProgress(STATE_CHECK_OK, null); + mCallback.reportProgress(STATE_CHECK_OK, null); } else { int progressState = STATE_CHECK_ERROR; final int exceptionType = result.getExceptionType(); @@ -575,358 +472,47 @@ public class AccountCheckSettingsFragment extends Fragment { progressState = STATE_CHECK_SHOW_SECURITY; break; } - reportProgress(progressState, result); + mCallback.reportProgress(progressState, result); } } } - private static String getErrorString(Context context, MessagingException ex) { - final int id; - String message = ex.getMessage(); - if (message != null) { - message = message.trim(); - } - switch (ex.getExceptionType()) { - // 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 = TextUtils.isEmpty(message) - ? R.string.account_setup_failed_dlg_certificate_message - : R.string.account_setup_failed_dlg_certificate_message_fmt; - break; - case MessagingException.AUTHENTICATION_FAILED: - id = R.string.account_setup_failed_dlg_auth_message; - break; - case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: - id = R.string.account_setup_autodiscover_dlg_authfail_message; - break; - case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR: - id = R.string.account_setup_failed_check_credentials_message; - break; - case MessagingException.IOERROR: - id = R.string.account_setup_failed_ioerror; - break; - case MessagingException.TLS_REQUIRED: - id = R.string.account_setup_failed_tls_required; - break; - case MessagingException.AUTH_REQUIRED: - id = R.string.account_setup_failed_auth_required; - break; - case MessagingException.SECURITY_POLICIES_UNSUPPORTED: - id = R.string.account_setup_failed_security_policies_unsupported; - // Belt and suspenders here; there should always be a non-empty array here - String[] unsupportedPolicies = (String[]) ex.getExceptionData(); - if (unsupportedPolicies == null) { - LogUtils.w(Logging.LOG_TAG, "No data for unsupported policies"); - break; - } - // Build a string, concatenating policies we don't support - final StringBuilder sb = new StringBuilder(); - boolean first = true; - for (String policyName: unsupportedPolicies) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append(policyName); - } - message = sb.toString(); - break; - case MessagingException.ACCESS_DENIED: - id = R.string.account_setup_failed_access_denied; - break; - case MessagingException.PROTOCOL_VERSION_UNSUPPORTED: - id = R.string.account_setup_failed_protocol_unsupported; - break; - case MessagingException.GENERAL_SECURITY: - id = R.string.account_setup_failed_security; - break; - case MessagingException.CLIENT_CERTIFICATE_REQUIRED: - id = R.string.account_setup_failed_certificate_required; + /** + * Convert progress to message + */ + protected static String getProgressString(Context context, int progress) { + int stringId = 0; + switch (progress) { + case STATE_CHECK_AUTODISCOVER: + stringId = R.string.account_setup_check_settings_retr_info_msg; break; - case MessagingException.CLIENT_CERTIFICATE_ERROR: - id = R.string.account_setup_failed_certificate_inaccessible; + case STATE_START: + case STATE_CHECK_INCOMING: + stringId = R.string.account_setup_check_settings_check_incoming_msg; break; - default: - id = TextUtils.isEmpty(message) - ? R.string.account_setup_failed_dlg_server_message - : R.string.account_setup_failed_dlg_server_message_fmt; + case STATE_CHECK_OUTGOING: + stringId = R.string.account_setup_check_settings_check_outgoing_msg; break; } - return TextUtils.isEmpty(message) - ? context.getString(id) - : context.getString(id, message); - } - - /** - * 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. - */ - public static class CheckingDialog extends DialogFragment { - @SuppressWarnings("hiding") - public final static String TAG = "CheckProgressDialog"; - - // Extras for saved instance state - private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress"; - - // UI - private String mProgressString; - - // Public no-args constructor needed for fragment re-instantiation - public CheckingDialog() {} - - /** - * Create a dialog that reports progress - * @param progress initial progress indication - */ - public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment, - int progress) { - final CheckingDialog f = new CheckingDialog(); - f.setTargetFragment(parentFragment, progress); - return f; - } - - /** - * Update the progress of an existing dialog - * @param progress latest progress to be displayed - */ - public void updateProgress(int progress) { - mProgressString = getProgressString(progress); - final AlertDialog dialog = (AlertDialog) getDialog(); - if (dialog != null && mProgressString != null) { - dialog.setMessage(mProgressString); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - if (savedInstanceState != null) { - mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING); - } - if (mProgressString == null) { - mProgressString = getProgressString(getTargetRequestCode()); - } - - final ProgressDialog dialog = new ProgressDialog(context); - dialog.setIndeterminate(true); - dialog.setMessage(mProgressString); - dialog.setButton(DialogInterface.BUTTON_NEGATIVE, - context.getString(R.string.cancel_action), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - - final AccountCheckSettingsFragment target = - (AccountCheckSettingsFragment) getTargetFragment(); - if (target != null) { - target.onCheckingDialogCancel(); - } - } - }); - return dialog; - } - - /** - * Listen for cancellation, which can happen from places other than the - * negative button (e.g. touching outside the dialog), and stop the checker - */ - @Override - public void onCancel(DialogInterface dialog) { - final AccountCheckSettingsFragment target = - (AccountCheckSettingsFragment) getTargetFragment(); - if (target != null) { - target.onCheckingDialogCancel(); - } - super.onCancel(dialog); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(EXTRA_PROGRESS_STRING, mProgressString); - } - - /** - * Convert progress to message - */ - private String getProgressString(int progress) { - int stringId = 0; - switch (progress) { - case STATE_CHECK_AUTODISCOVER: - stringId = R.string.account_setup_check_settings_retr_info_msg; - break; - case STATE_CHECK_INCOMING: - stringId = R.string.account_setup_check_settings_check_incoming_msg; - break; - case STATE_CHECK_OUTGOING: - stringId = R.string.account_setup_check_settings_check_outgoing_msg; - break; - } - return getActivity().getString(stringId); - } - } - - /** - * The standard error dialog. Calls back to onErrorDialogButton(). - */ - public static class ErrorDialog extends DialogFragment { - @SuppressWarnings("hiding") - public final static String TAG = "ErrorDialog"; - - // Bundle keys for arguments - private final static String ARGS_MESSAGE = "ErrorDialog.Message"; - private final static String ARGS_EXCEPTION_ID = "ErrorDialog.ExceptionId"; - - /** - * Use {@link #newInstance} This public constructor is still required so - * that DialogFragment state can be automatically restored by the - * framework. - */ - public ErrorDialog() { - } - - public static ErrorDialog newInstance(Context context, AccountCheckSettingsFragment target, - MessagingException ex) { - final ErrorDialog fragment = new ErrorDialog(); - final Bundle arguments = new Bundle(2); - arguments.putString(ARGS_MESSAGE, getErrorString(context, ex)); - arguments.putInt(ARGS_EXCEPTION_ID, ex.getExceptionType()); - fragment.setArguments(arguments); - fragment.setTargetFragment(target, 0); - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final Bundle arguments = getArguments(); - final String message = arguments.getString(ARGS_MESSAGE); - final int exceptionId = arguments.getInt(ARGS_EXCEPTION_ID); - final AccountCheckSettingsFragment target = - (AccountCheckSettingsFragment) getTargetFragment(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setMessage(message) - .setCancelable(true); - - // Use a different title when we get - // MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED - if (exceptionId == MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED) { - builder.setTitle(R.string.account_setup_autodiscover_dlg_authfail_title); - } else { - builder.setIconAttribute(android.R.attr.alertDialogIcon) - .setTitle(context.getString(R.string.account_setup_failed_dlg_title)); - } - - if (exceptionId == MessagingException.CLIENT_CERTIFICATE_REQUIRED) { - // Certificate error - show two buttons so the host fragment can auto pop - // into the appropriate flow. - builder.setPositiveButton( - context.getString(android.R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - target.onEditCertificateOk(); - } - }); - builder.setNegativeButton( - context.getString(android.R.string.cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - target.onErrorDialogEditButton(); - } - }); - - } else { - // "Normal" error - just use a single "Edit details" button. - builder.setPositiveButton( - context.getString(R.string.account_setup_failed_dlg_edit_details_action), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - target.onErrorDialogEditButton(); - } - }); - } - - return builder.create(); + if (stringId != 0) { + return context.getString(stringId); + } else { + return null; } - } /** - * 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.) + * Convert mode to initial progress */ - public static class SecurityRequiredDialog extends DialogFragment { - @SuppressWarnings("hiding") - public final static String TAG = "SecurityRequiredDialog"; - - // Bundle keys for arguments - private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName"; - - // Public no-args constructor needed for fragment re-instantiation - public SecurityRequiredDialog() {} - - public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target, - String hostName) { - final SecurityRequiredDialog fragment = new SecurityRequiredDialog(); - final Bundle arguments = new Bundle(1); - arguments.putString(ARGS_HOST_NAME, hostName); - fragment.setArguments(arguments); - fragment.setTargetFragment(target, 0); - return fragment; + protected static int getProgressForMode(int checkMode) { + switch (checkMode) { + case SetupDataFragment.CHECK_INCOMING: + return STATE_CHECK_INCOMING; + case SetupDataFragment.CHECK_OUTGOING: + return STATE_CHECK_OUTGOING; + case SetupDataFragment.CHECK_AUTODISCOVER: + return STATE_CHECK_AUTODISCOVER; } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final Bundle arguments = getArguments(); - final String hostName = arguments.getString(ARGS_HOST_NAME); - final AccountCheckSettingsFragment target = - (AccountCheckSettingsFragment) getTargetFragment(); - - return new AlertDialog.Builder(context) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setTitle(context.getString(R.string.account_setup_security_required_title)) - .setMessage(context.getString( - R.string.account_setup_security_policies_required_fmt, hostName)) - .setCancelable(true) - .setPositiveButton( - context.getString(R.string.okay_action), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - target.onSecurityRequiredDialogResultOk(true); - } - }) - .setNegativeButton( - context.getString(R.string.cancel_action), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - target.onSecurityRequiredDialogResultOk(false); - } - }) - .create(); - } - + return STATE_START; } - } diff --git a/src/com/android/email/activity/setup/AccountCreationFragment.java b/src/com/android/email/activity/setup/AccountCreationFragment.java index bca76cf7b..e5736774c 100644 --- a/src/com/android/email/activity/setup/AccountCreationFragment.java +++ b/src/com/android/email/activity/setup/AccountCreationFragment.java @@ -44,6 +44,8 @@ import java.io.IOException; * related background tasks. */ public class AccountCreationFragment extends Fragment { + public static final String TAG = "AccountCreationFragment"; + public static final int REQUEST_CODE_ACCEPT_POLICIES = 1; private static final String ACCOUNT_TAG = "account"; @@ -62,6 +64,13 @@ public class AccountCreationFragment extends Fragment { private Context mAppContext; private final Handler mHandler; + public interface Callback { + void onAccountCreationFragmentComplete(); + void destroyAccountCreationFragment(); + void showCreateAccountErrorDialog(); + void setAccount(Account account); + } + public AccountCreationFragment() { mHandler = new Handler(); } @@ -84,13 +93,18 @@ public class AccountCreationFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); - mAppContext = getActivity().getApplicationContext(); if (savedInstanceState != null) { mStage = savedInstanceState.getInt(SAVESTATE_STAGE); } } @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mAppContext = getActivity().getApplicationContext(); + } + + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(SAVESTATE_STAGE, mStage); @@ -210,7 +224,7 @@ public class AccountCreationFragment extends Fragment { mStage = STAGE_REFRESHING_ACCOUNT; kickRefreshingAccountLoader(); } else { - final AccountSetupFinal activity = (AccountSetupFinal)getActivity(); + final Callback callback = (Callback) getActivity(); mHandler.post(new Runnable() { @Override public void run() { @@ -218,8 +232,8 @@ public class AccountCreationFragment extends Fragment { return; } // Can't do this from within onLoadFinished - activity.destroyAccountCreationFragment(); - activity.showCreateAccountErrorDialog(); + callback.destroyAccountCreationFragment(); + callback.showCreateAccountErrorDialog(); } }); } @@ -303,13 +317,13 @@ public class AccountCreationFragment extends Fragment { } // Move to final setup screen - AccountSetupFinal activity = (AccountSetupFinal) getActivity(); - activity.getSetupData().setAccount(account); - activity.proceedFromAccountCreationFragment(); + Callback callback = (Callback) getActivity(); + callback.setAccount(account); + callback.onAccountCreationFragmentComplete(); // Update the folder list (to get our starting folders, e.g. Inbox) - final EmailServiceProxy proxy = EmailServiceUtils.getServiceForAccount(activity, - account.mId); + final EmailServiceProxy proxy = EmailServiceUtils + .getServiceForAccount(mAppContext, account.mId); try { proxy.updateFolderList(account.mId); } catch (RemoteException e) { diff --git a/src/com/android/email/activity/setup/AccountCredentials.java b/src/com/android/email/activity/setup/AccountCredentials.java new file mode 100644 index 000000000..9b2b43cd3 --- /dev/null +++ b/src/com/android/email/activity/setup/AccountCredentials.java @@ -0,0 +1,77 @@ +package com.android.email.activity.setup; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import com.android.email.R; +import com.android.email.activity.UiUtilities; + +public class AccountCredentials extends Activity + implements AccountSetupCredentialsFragment.Callback, View.OnClickListener { + + private View mDoneButton; + private View mCancelButton; + + private static final String EXTRA_EMAIL = "email"; + private static final String EXTRA_PROTOCOL = "protocol"; + + private static final String CREDENTIALS_FRAGMENT_TAG = "credentials"; + + public static Intent getAccountCredentialsIntent(final Context context, final String email, + final String protocol) { + final Intent i = new Intent(context, AccountCredentials.class); + i.putExtra(EXTRA_EMAIL, email); + i.putExtra(EXTRA_PROTOCOL, protocol); + return i; + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.account_credentials); + final String emailAddress = getIntent().getStringExtra(EXTRA_EMAIL); + final String protocol = getIntent().getStringExtra(EXTRA_PROTOCOL); + + final AccountSetupCredentialsFragment f = + AccountSetupCredentialsFragment.newInstance(emailAddress, protocol); + getFragmentManager().beginTransaction() + .add(R.id.account_credentials_fragment_container, f, CREDENTIALS_FRAGMENT_TAG) + .commit(); + + mDoneButton = UiUtilities.getView(this, R.id.done); + mCancelButton = UiUtilities.getView(this, R.id.cancel); + mDoneButton.setOnClickListener(this); + mCancelButton.setOnClickListener(this); + + // Assume canceled until we find out otherwise. + setResult(RESULT_CANCELED); + } + + @Override + public void onCredentialsComplete(Bundle results) { + final Intent intent = new Intent(); + intent.putExtras(results); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public void onClick(View view) { + if (view == mDoneButton) { + final AccountSetupCredentialsFragment fragment = (AccountSetupCredentialsFragment) + getFragmentManager().findFragmentByTag(CREDENTIALS_FRAGMENT_TAG); + final Bundle results = fragment.getCredentialResults(); + onCredentialsComplete(results); + } else if (view == mCancelButton) { + finish(); + } + } + + @Override + public void setNextButtonEnabled(boolean enabled) { + mDoneButton.setEnabled(enabled); + } +} diff --git a/src/com/android/email/activity/setup/AccountFinalizeFragment.java b/src/com/android/email/activity/setup/AccountFinalizeFragment.java new file mode 100644 index 000000000..4880d0de2 --- /dev/null +++ b/src/com/android/email/activity/setup/AccountFinalizeFragment.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 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 android.app.Fragment; +import android.app.LoaderManager; +import android.content.ContentValues; +import android.content.Context; +import android.content.Loader; +import android.os.Bundle; +import android.os.Handler; + +import com.android.email.provider.AccountBackupRestore; +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent; +import com.android.mail.ui.MailAsyncTaskLoader; + +public class AccountFinalizeFragment extends Fragment { + public static final String TAG = "AccountFinalizeFragment"; + + private static final String ACCOUNT_TAG = "account"; + + private static final int FINAL_ACCOUNT_TASK_LOADER_ID = 0; + + private Context mAppContext; + private final Handler mHandler = new Handler(); + + public interface Callback { + void onAccountFinalizeFragmentComplete(); + } + + public AccountFinalizeFragment() {} + + public static AccountFinalizeFragment newInstance(Account account) { + final AccountFinalizeFragment f = new AccountFinalizeFragment(); + final Bundle args = new Bundle(1); + args.putParcelable(ACCOUNT_TAG, account); + f.setArguments(args); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mAppContext = getActivity().getApplicationContext(); + + setRetainInstance(true); + } + + @Override + public void onResume() { + super.onResume(); + + getLoaderManager().initLoader(FINAL_ACCOUNT_TASK_LOADER_ID, getArguments(), + new LoaderManager.LoaderCallbacks<Boolean>() { + @Override + public Loader<Boolean> onCreateLoader(int id, Bundle args) { + final Account accountArg = args.getParcelable(ACCOUNT_TAG); + return new FinalSetupTaskLoader(mAppContext, accountArg); + } + + @Override + public void onLoadFinished(Loader<Boolean> loader, Boolean success) { + if (!success) { + return; + } + mHandler.post(new Runnable() { + @Override + public void run() { + if (isResumed()) { + Callback activity = (Callback) getActivity(); + activity.onAccountFinalizeFragmentComplete(); + } + } + }); + + } + + @Override + public void onLoaderReset(Loader<Boolean> loader) {} + }); + } + + /** + * Final account setup work is handled in this Loader: + * Commit final values to provider + * Trigger account backup + */ + private static class FinalSetupTaskLoader extends MailAsyncTaskLoader<Boolean> { + + private final Account mAccount; + + public FinalSetupTaskLoader(Context context, Account account) { + super(context); + mAccount = account; + } + + Account getAccount() { + return mAccount; + } + + @Override + public Boolean loadInBackground() { + // Update the account in the database + final ContentValues cv = new ContentValues(); + cv.put(EmailContent.AccountColumns.DISPLAY_NAME, mAccount.getDisplayName()); + cv.put(EmailContent.AccountColumns.SENDER_NAME, mAccount.getSenderName()); + mAccount.update(getContext(), cv); + + // Update the backup (side copy) of the accounts + AccountBackupRestore.backup(getContext()); + + return true; + } + + @Override + protected void onDiscardResult(Boolean result) {} + } +} diff --git a/src/com/android/email/activity/setup/AccountServerBaseFragment.java b/src/com/android/email/activity/setup/AccountServerBaseFragment.java index 3c136b192..c9b8e20ee 100644 --- a/src/com/android/email/activity/setup/AccountServerBaseFragment.java +++ b/src/com/android/email/activity/setup/AccountServerBaseFragment.java @@ -17,18 +17,17 @@ package com.android.email.activity.setup; import android.app.Activity; -import android.app.Fragment; +import android.app.LoaderManager; import android.content.Context; -import android.os.AsyncTask; +import android.content.Loader; import android.os.Bundle; +import android.os.Handler; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -45,64 +44,49 @@ import com.android.emailcommon.provider.HostAuth; * Activity callback during onAttach * Present "Next" button and respond to its clicks */ -public abstract class AccountServerBaseFragment extends Fragment - implements AccountCheckSettingsFragment.Callbacks, OnClickListener { +public abstract class AccountServerBaseFragment extends AccountSetupFragment + implements OnClickListener { private static final String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings"; private static final String BUNDLE_KEY_ACTIVITY_TITLE = "AccountServerBaseFragment.title"; + private static final String BUNDLE_KEY_SAVING = "AccountServerBaseFragment.saving"; - protected Activity mContext; - protected Callback mCallback = EmptyCallback.INSTANCE; + protected Context mAppContext; /** * Whether or not we are in "settings mode". We re-use the same screens for both the initial - * account creation as well as subsequent account modification. If <code>mSettingsMode</code> - * if <code>false</code>, we are in account creation mode. Otherwise, we are in account + * account creation as well as subsequent account modification. If this is + * <code>false</code>, we are in account creation mode. Otherwise, we are in account * modification mode. */ protected boolean mSettingsMode; - /*package*/ HostAuth mLoadedSendAuth; - /*package*/ HostAuth mLoadedRecvAuth; + protected HostAuth mLoadedSendAuth; + protected HostAuth mLoadedRecvAuth; protected SetupDataFragment mSetupData; // This is null in the setup wizard screens, and non-null in AccountSettings mode private View mProceedButton; + protected String mBaseScheme = "protocol"; - // This is used to debounce multiple clicks on the proceed button (which does async work) - private boolean mProceedButtonPressed; - /*package*/ String mBaseScheme = "protocol"; + // Set to true if we're in the process of saving + private boolean mSaving; /** - * Callback interface that owning activities must provide + // Used to post the callback once we're done saving, since we can't perform fragment + // transactions from {@link LoaderManager.LoaderCallbacks#onLoadFinished(Loader, Object)} */ - public interface Callback { - /** - * Called each time the user-entered input transitions between valid and invalid - * @param enable true to enable proceed/next button, false to disable - */ - public void onEnableProceedButtons(boolean enable); + private Handler mHandler = new Handler(); + /** + * Callback interface that owning activities must provide + */ + public interface Callback extends AccountSetupFragment.Callback { /** * Called when user clicks "next". Starts account checker. * @param checkMode values from {@link SetupDataFragment} - * @param target the fragment that requested the check - */ - public void onProceedNext(int checkMode, AccountServerBaseFragment target); - - /** - * 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 setupData possibly modified SetupData */ - public void onCheckSettingsComplete(int result, SetupDataFragment setupData); - } - - 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 onCheckSettingsComplete(int result, SetupDataFragment setupData) { } + public void onAccountServerUIComplete(int checkMode); + public void onAccountServerSaveComplete(); } /** @@ -111,7 +95,7 @@ public abstract class AccountServerBaseFragment extends Fragment * @param settingsMode True if we're in settings, false if we're in account creation * @return Arg bundle */ - public static Bundle getArgs(Boolean settingsMode) { + public static Bundle getArgs(boolean settingsMode) { final Bundle setupModeArgs = new Bundle(1); setupModeArgs.putBoolean(BUNDLE_KEY_SETTINGS, settingsMode); return setupModeArgs; @@ -130,6 +114,7 @@ public abstract class AccountServerBaseFragment extends Fragment mSettingsMode = false; if (savedInstanceState != null) { mSettingsMode = savedInstanceState.getBoolean(BUNDLE_KEY_SETTINGS); + mSaving = savedInstanceState.getBoolean(BUNDLE_KEY_SAVING); } else if (getArguments() != null) { mSettingsMode = getArguments().getBoolean(BUNDLE_KEY_SETTINGS); } @@ -150,38 +135,40 @@ public abstract class AccountServerBaseFragment extends Fragment @Override public void onActivityCreated(Bundle savedInstanceState) { - // startPreferencePanel launches this fragment with the right title initially, but - // if the device is rotate we must set the title ourselves - mContext = getActivity(); + final Activity activity = getActivity(); + mAppContext = activity.getApplicationContext(); if (mSettingsMode && savedInstanceState != null) { - mContext.setTitle(savedInstanceState.getString(BUNDLE_KEY_ACTIVITY_TITLE)); + // startPreferencePanel launches this fragment with the right title initially, but + // if the device is rotated we must set the title ourselves + activity.setTitle(savedInstanceState.getString(BUNDLE_KEY_ACTIVITY_TITLE)); } SetupDataFragment.SetupDataContainer container = - (SetupDataFragment.SetupDataContainer) mContext; + (SetupDataFragment.SetupDataContainer) activity; mSetupData = container.getSetupData(); super.onActivityCreated(savedInstanceState); } @Override - public void onSaveInstanceState(Bundle outState) { - outState.putString(BUNDLE_KEY_ACTIVITY_TITLE, (String) getActivity().getTitle()); - outState.putBoolean(BUNDLE_KEY_SETTINGS, mSettingsMode); + public void onResume() { + super.onResume(); + if (mSaving) { + // We need to call this here in case the save completed while we weren't resumed + saveSettings(); + } } @Override - public void onDetach() { - super.onDetach(); - - // Ensure that we don't have any callbacks at this point. - mCallback = EmptyCallback.INSTANCE; + public void onSaveInstanceState(Bundle outState) { + outState.putString(BUNDLE_KEY_ACTIVITY_TITLE, (String) getActivity().getTitle()); + outState.putBoolean(BUNDLE_KEY_SETTINGS, mSettingsMode); } @Override public void onPause() { // Hide the soft keyboard if we lose focus final InputMethodManager imm = - (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + (InputMethodManager) mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); super.onPause(); } @@ -196,25 +183,12 @@ public abstract class AccountServerBaseFragment extends Fragment getActivity().onBackPressed(); break; case R.id.done: - // Simple debounce - just ignore while checks are underway - if (mProceedButtonPressed) { - return; - } - mProceedButtonPressed = true; - onNext(); + collectUserInput(); break; } } /** - * Activity provides callbacks here. - */ - public void setCallback(Callback callback) { - mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; - mContext = getActivity(); - } - - /** * Enable/disable the "next" button */ public void enableNextButton(boolean enable) { @@ -223,10 +197,11 @@ public abstract class AccountServerBaseFragment extends Fragment if (mProceedButton != null) { mProceedButton.setEnabled(enable); } - clearButtonBounce(); - // TODO: This supports the phone UX activities and will be removed - mCallback.onEnableProceedButtons(enable); + final Callback callback = (Callback) getActivity(); + if (callback != null) { + callback.setNextButtonEnabled(enable); + } } /** @@ -243,7 +218,7 @@ public abstract class AccountServerBaseFragment extends Fragment public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { // Framework will not auto-hide IME; do it ourselves - InputMethodManager imm = (InputMethodManager)mContext. + InputMethodManager imm = (InputMethodManager) mAppContext. getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); view.setError(errorMessage); @@ -292,80 +267,49 @@ public abstract class AccountServerBaseFragment extends Fragment }; /** - * Clears the "next" button de-bounce flags and allows the "next" button to activate. - */ - protected void clearButtonBounce() { - mProceedButtonPressed = false; - } - - /** - * Implements AccountCheckSettingsFragment.Callbacks - * - * Handle OK or error result from check settings. Save settings (async), and then - * exit to previous fragment. - */ - @Override - public void onCheckSettingsComplete(final int settingsResult, SetupDataFragment setupData) { - mSetupData = setupData; - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - if (settingsResult == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { - if (mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_EDIT) { - saveSettingsAfterEdit(); - } else { - saveSettingsAfterSetup(); - } - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - // Signal to owning activity that a settings check completed - mCallback.onCheckSettingsComplete(settingsResult, mSetupData); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - /** - * Implements AccountCheckSettingsFragment.Callbacks - * This is overridden only by AccountSetupExchange - */ - @Override - public void onAutoDiscoverComplete(int result, SetupDataFragment setupData) { - throw new IllegalStateException(); - } - - /** * Returns whether or not any settings have changed. */ public boolean haveSettingsChanged() { final Account account = mSetupData.getAccount(); - final HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); + final HostAuth sendAuth = account.getOrCreateHostAuthSend(mAppContext); final boolean sendChanged = (mLoadedSendAuth != null && !mLoadedSendAuth.equals(sendAuth)); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mAppContext); final boolean recvChanged = (mLoadedRecvAuth != null && !mLoadedRecvAuth.equals(recvAuth)); return sendChanged || recvChanged; } - /** - * Save settings after "OK" result from checker. Concrete classes must implement. - * This is called from a worker thread and is allowed to perform DB operations. - */ - public abstract void saveSettingsAfterEdit(); + public void saveSettings() { + getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Boolean>() { + @Override + public Loader<Boolean> onCreateLoader(int id, Bundle args) { + return getSaveSettingsLoader(); + } - /** - * Save settings after "OK" result from checker. Concrete classes must implement. - * This is called from a worker thread and is allowed to perform DB operations. - */ - public abstract void saveSettingsAfterSetup(); + @Override + public void onLoadFinished(Loader<Boolean> loader, Boolean data) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (isResumed()) { + final Callback callback = (Callback) getActivity(); + callback.onAccountServerSaveComplete(); + } + } + }); + } + + @Override + public void onLoaderReset(Loader<Boolean> loader) {} + }); + } + + public abstract Loader<Boolean> getSaveSettingsLoader(); /** - * Respond to a click of the "Next" button. Concrete classes must implement. + * Collect the user's input into the setup data object. Concrete classes must implement. */ - public abstract void onNext(); + public abstract void collectUserInput(); } diff --git a/src/com/android/email/activity/setup/AccountSettings.java b/src/com/android/email/activity/setup/AccountSettings.java index 9cce53cda..2e9ef22d2 100644 --- a/src/com/android/email/activity/setup/AccountSettings.java +++ b/src/com/android/email/activity/setup/AccountSettings.java @@ -17,7 +17,6 @@ package com.android.email.activity.setup; import android.app.ActionBar; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -72,7 +71,9 @@ import java.util.List; * dealing with accounts being added/deleted and triggering the header reload. */ public class AccountSettings extends PreferenceActivity implements FeedbackEnabledActivity, - SetupDataFragment.SetupDataContainer { + SetupDataFragment.SetupDataContainer, SecurityRequiredDialogFragment.Callback, + CheckSettingsErrorDialogFragment.Callback, AccountCheckSettingsFragment.Callback, + AccountServerBaseFragment.Callback { /* * Intent to open account settings for account=1 adb shell am start -a android.intent.action.EDIT \ @@ -113,7 +114,8 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl private long mRequestedAccountId; private Header[] mAccountListHeaders; private Header mAppPreferencesHeader; - /* package */ Fragment mCurrentFragment; + private static final String CURRENT_FRAGMENT_TAG = "currentFragment"; + private Bundle mCurrentFragmentBundle = new Bundle(1); private long mDeletingAccountId = -1; private boolean mShowDebugMenu; private List<Header> mGeneratedHeaders; @@ -130,15 +132,6 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl // Specific callbacks used by settings fragments private final AccountSettingsFragmentCallback mAccountSettingsFragmentCallback = new AccountSettingsFragmentCallback(); - private final AccountServerSettingsFragmentCallback mAccountServerSettingsFragmentCallback - = new AccountServerSettingsFragmentCallback(); - - /** - * Display (and edit) settings for a specific account, or -1 for any/all accounts - */ - public static void actionSettings(Activity fromActivity, long accountId) { - fromActivity.startActivity(createAccountSettingsIntent(accountId, null, null)); - } /** * Create and return an intent to display (and edit) settings for a specific account, or -1 @@ -209,7 +202,8 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl launchMailboxSettings(i); return; } else if (i.hasExtra(EXTRA_NO_ACCOUNTS)) { - AccountSetupBasics.actionNewAccountWithResult(this); + final Intent setupIntent = AccountSetupFinal.actionNewAccountWithResultIntent(this); + startActivity(setupIntent); finish(); return; } else { @@ -375,8 +369,9 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl */ @Override public void onBackPressed() { - if (mCurrentFragment instanceof AccountServerBaseFragment) { - if (((AccountServerBaseFragment) mCurrentFragment).haveSettingsChanged()) { + final Fragment currentFragment = getCurrentFragment(); + if (currentFragment instanceof AccountServerBaseFragment) { + if (((AccountServerBaseFragment) currentFragment).haveSettingsChanged()) { UnsavedChangesDialogFragment dialogFragment = UnsavedChangesDialogFragment.newInstanceForBack(); dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); @@ -407,7 +402,8 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl } private void onAddNewAccount() { - AccountSetupBasics.actionNewAccount(this); + final Intent setupIntent = AccountSetupFinal.actionNewAccountIntent(this); + startActivity(setupIntent); } /** @@ -558,8 +554,9 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl @Override public void onHeaderClick(Header header, int position) { // special case when exiting the server settings fragments - if ((mCurrentFragment instanceof AccountServerBaseFragment) - && (((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged())) { + final Fragment currentFragment = getCurrentFragment(); + if ((currentFragment instanceof AccountServerBaseFragment) + && (((AccountServerBaseFragment)currentFragment).haveSettingsChanged())) { UnsavedChangesDialogFragment dialogFragment = UnsavedChangesDialogFragment.newInstanceForHeader(position); dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); @@ -586,8 +583,6 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl * with a dialog, and the user OK'd it. */ private void forceSwitchHeader(int position) { - // Clear the current fragment; we're navigating away - mCurrentFragment = null; // Ensure the UI visually shows the correct header selected setSelection(position); switchToHeader(mGeneratedHeaders.get(position)); @@ -597,8 +592,6 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl * Forcefully go backward in the stack. This may potentially discard unsaved settings. */ private void forceBack() { - // Clear the current fragment; we're navigating away - mCurrentFragment = null; onBackPressed(); } @@ -609,19 +602,27 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl if (f instanceof AccountSettingsFragment) { final AccountSettingsFragment asf = (AccountSettingsFragment) f; asf.setCallback(mAccountSettingsFragmentCallback); - } else if (f instanceof AccountServerBaseFragment) { - final AccountServerBaseFragment asbf = (AccountServerBaseFragment) f; - asbf.setCallback(mAccountServerSettingsFragmentCallback); - } else { + } else if (!(f instanceof AccountServerBaseFragment)) { // Possibly uninteresting fragment, such as a dialog. return; } - mCurrentFragment = f; + // By some internal witchcraft, this will persist a reference to this fragment across + // configuration changes + getFragmentManager().putFragment(mCurrentFragmentBundle, CURRENT_FRAGMENT_TAG, f); // When we're changing fragments, enable/disable the add account button invalidateOptionsMenu(); } + public Fragment getCurrentFragment() { + try { + return getFragmentManager().getFragment(mCurrentFragmentBundle, CURRENT_FRAGMENT_TAG); + } catch (final IllegalStateException e) { + LogUtils.d(LogUtils.TAG, e, "Could not find current fragment, returning null"); + return null; + } + } + /** * Callbacks for AccountSettingsFragment */ @@ -648,34 +649,90 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl } } + @Override + public void setNextButtonEnabled(boolean enabled) {} + /** - * Callbacks for AccountServerSettingsFragmentCallback + * Save process is done, dismiss the fragment. */ - private class AccountServerSettingsFragmentCallback - implements AccountServerBaseFragment.Callback { - @Override - public void onEnableProceedButtons(boolean enable) { - // This is not used - it's a callback for the legacy activities - } + @Override + public void onAccountServerSaveComplete() { + onBackPressed(); + } - @Override - public void onProceedNext(int checkMode, AccountServerBaseFragment target) { - AccountCheckSettingsFragment checkerFragment = - AccountCheckSettingsFragment.newInstance(checkMode, target); - startPreferenceFragment(checkerFragment, true); + @Override + public void onAccountServerUIComplete(int checkMode) { + Fragment checkerDialog = CheckSettingsProgressDialogFragment.newInstance(checkMode); + Fragment checkerFragment = AccountCheckSettingsFragment.newInstance(checkMode); + getFragmentManager().beginTransaction() + .add(checkerDialog, CheckSettingsProgressDialogFragment.TAG) + .add(checkerFragment, AccountCheckSettingsFragment.TAG) + .commit(); + } + + /** + * After verifying a new server configuration as OK, we return here and continue. This kicks + * off the save process. + */ + @Override + public void onCheckSettingsComplete() { + dismissCheckSettingsFragment(); + final AccountServerBaseFragment f = (AccountServerBaseFragment) getCurrentFragment(); + f.saveSettings(); + } + + @Override + public void onCheckSettingsSecurityRequired(String hostName) { + dismissCheckSettingsFragment(); + SecurityRequiredDialogFragment.newInstance(hostName) + .show(getFragmentManager(), SecurityRequiredDialogFragment.TAG); + } + + @Override + public void onCheckSettingsError(int reason, String message) { + dismissCheckSettingsFragment(); + CheckSettingsErrorDialogFragment.newInstance(reason, message) + .show(getFragmentManager(), CheckSettingsErrorDialogFragment.TAG); + } + + @Override + public void onCheckSettingsAutoDiscoverComplete(int result) { + throw new IllegalStateException(); + } + + private void dismissCheckSettingsFragment() { + final Fragment f = + getFragmentManager().findFragmentByTag(AccountCheckSettingsFragment.TAG); + final Fragment d = + getFragmentManager().findFragmentByTag(CheckSettingsProgressDialogFragment.TAG); + getFragmentManager().beginTransaction() + .remove(f) + .remove(d) + .commit(); + } + + @Override + public void onSecurityRequiredDialogResult(boolean ok) { + if (ok) { + final AccountServerBaseFragment f = (AccountServerBaseFragment) getCurrentFragment(); + f.saveSettings(); } + // else just stay here + } - /** - * After verifying a new server configuration as OK, we return here and continue. This - * simply does a "back" to exit the settings screen. - */ - @Override - public void onCheckSettingsComplete(int result, SetupDataFragment setupData) { - if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { - // Settings checked & saved; clear current fragment - mCurrentFragment = null; - onBackPressed(); - } + @Override + public void onCheckSettingsErrorDialogEditSettings() { + // Just stay here + } + + @Override + public void onCheckSettingsErrorDialogEditCertificate() { + final Fragment f = getCurrentFragment(); + if (f instanceof AccountSetupIncomingFragment) { + AccountSetupIncomingFragment asif = (AccountSetupIncomingFragment) f; + asif.onCertificateRequested(); + } else { + LogUtils.wtf(LogUtils.TAG, "Tried to change cert on non-incoming screen?"); } } @@ -716,8 +773,7 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl public void onIncomingSettings(Account account) { try { mSetupData = new SetupDataFragment(SetupDataFragment.FLOW_MODE_EDIT, account); - final Fragment f = new AccountSetupIncomingFragment(); - f.setArguments(AccountSetupIncomingFragment.getArgs(true)); + final Fragment f = AccountSetupIncomingFragment.newInstance(true); // Use startPreferenceFragment here because we need to keep this activity instance startPreferenceFragment(f, true); } catch (Exception e) { @@ -727,14 +783,11 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl /** * Dispatch to edit outgoing settings. - * - * TODO: Make things less hardwired */ public void onOutgoingSettings(Account account) { try { mSetupData = new SetupDataFragment(SetupDataFragment.FLOW_MODE_EDIT, account); - final Fragment f = new AccountSetupOutgoingFragment(); - f.setArguments(AccountSetupOutgoingFragment.getArgs(true)); + final Fragment f = AccountSetupOutgoingFragment.newInstance(true); // Use startPreferenceFragment here because we need to keep this activity instance startPreferenceFragment(f, true); } catch (Exception e) { @@ -939,9 +992,4 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl public SetupDataFragment getSetupData() { return mSetupData; } - - @Override - public void setSetupData(SetupDataFragment setupData) { - mSetupData = setupData; - } } diff --git a/src/com/android/email/activity/setup/AccountSetupActivity.java b/src/com/android/email/activity/setup/AccountSetupActivity.java index fd6e63da0..1f6ab4e9f 100644 --- a/src/com/android/email/activity/setup/AccountSetupActivity.java +++ b/src/com/android/email/activity/setup/AccountSetupActivity.java @@ -20,15 +20,11 @@ import android.app.Activity; import android.app.FragmentTransaction; import android.os.Bundle; -import com.android.emailcommon.Logging; -import com.android.mail.utils.LogUtils; - /** * Superclass of all of the account setup activities; ensures that SetupData state is saved/restored * automatically as required */ public class AccountSetupActivity extends Activity implements SetupDataFragment.SetupDataContainer { - private static final boolean DEBUG_SETUP_FLOWS = false; // Don't check in set to true protected SetupDataFragment mSetupData; private static final String SETUP_DATA_FRAGMENT_TAG = "setupData"; @@ -57,20 +53,10 @@ public class AccountSetupActivity extends Activity implements SetupDataFragment. ft.add(mSetupData, SETUP_DATA_FRAGMENT_TAG); ft.commit(); } - - if (DEBUG_SETUP_FLOWS) { - LogUtils.d(Logging.LOG_TAG, "%s onCreate %s", getClass().getName(), - mSetupData.debugString()); - } } @Override public SetupDataFragment getSetupData() { return mSetupData; } - - @Override - public void setSetupData(SetupDataFragment setupData) { - mSetupData = setupData; - } } diff --git a/src/com/android/email/activity/setup/AccountSetupBasics.java b/src/com/android/email/activity/setup/AccountSetupBasics.java deleted file mode 100644 index 89ba7a112..000000000 --- a/src/com/android/email/activity/setup/AccountSetupBasics.java +++ /dev/null @@ -1,642 +0,0 @@ -/* - * Copyright (C) 2008 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 android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentTransaction; -import android.app.LoaderManager; -import android.content.Context; -import android.content.CursorLoader; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.Loader; -import android.database.Cursor; -import android.graphics.Paint; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.ContactsContract; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.email.R; -import com.android.email.activity.UiUtilities; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.emailcommon.Logging; -import com.android.emailcommon.VendorPolicyLoader.Provider; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.Credential; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.HostAuth; -import com.android.emailcommon.utility.Utility; -import com.android.mail.utils.LogUtils; - -import java.net.URISyntaxException; - -/** - * Prompts the user for the email address and password. Also prompts for "Use this account as - * default" if this is the 2nd+ account being set up. - * - * If the domain is well-known, the account is configured fully and checked immediately - * using AccountCheckSettingsFragment. If this succeeds we proceed directly to AccountSetupOptions. - * - * If the domain is not known, or the user selects Manual setup, we invoke the - * AccountSetupAccountType activity where the user can begin to manually configure the account. - * - * === Support for automated testing == - * This activity can also be launched directly via INTENT_CREATE_ACCOUNT. This is intended - * only for use by continuous test systems, and is currently only available when - * {@link ActivityManager#isRunningInTestHarness()} is set. To use this mode, you must construct - * an intent which contains all necessary information to create the account. No connection - * checking is done, so the account may or may not actually work. Here is a sample command, for a - * gmail account "test_account" with a password of "test_password". - * - * $ adb shell am start -a com.android.email.CREATE_ACCOUNT \ - * -e EMAIL test_account@gmail.com \ - * -e USER "Test Account Name" \ - * -e INCOMING imap+ssl+://test_account:test_password@imap.gmail.com \ - * -e OUTGOING smtp+ssl+://test_account:test_password@smtp.gmail.com - * - * Note: For accounts that require the full email address in the login, encode the @ as %40. - * Note: Exchange accounts that require device security policies cannot be created automatically. - */ -public class AccountSetupBasics extends AccountSetupActivity - implements OnClickListener { - - // STOPSHIP: Set to false before shipping, logs PII - private final static boolean ENTER_DEBUG_SCREEN = true; - - /** - * Direct access for forcing account creation - * For use by continuous automated test system (e.g. in conjunction with monkey tests) - */ - private static String INTENT_CREATE_ACCOUNT; - private static final String EXTRA_FLOW_MODE = "FLOW_MODE"; - private static final String EXTRA_FLOW_ACCOUNT_TYPE = "FLOW_ACCOUNT_TYPE"; - private static final String EXTRA_CREATE_ACCOUNT_EMAIL = "EMAIL"; - private static final String EXTRA_CREATE_ACCOUNT_USER = "USER"; - private static final String EXTRA_CREATE_ACCOUNT_INCOMING = "INCOMING"; - private static final String EXTRA_CREATE_ACCOUNT_OUTGOING = "OUTGOING"; - private static final Boolean DEBUG_ALLOW_NON_TEST_HARNESS_CREATION = false; - - private static final String STATE_KEY_PROVIDER = "AccountSetupBasics.provider"; - - // Support for UI - private Provider mProvider; - private TextView mManualButton; - private ImageButton mNextButton; - private boolean mNextButtonInhibit; - private boolean mPaused; - - private static final int OWNER_NAME_LOADER_ID = 0; - private String mOwnerName; - - public static void actionNewAccount(Activity fromActivity) { - final Intent i = new Intent(fromActivity, AccountSetupBasics.class); - i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NORMAL); - fromActivity.startActivity(i); - } - - public static void actionNewAccountWithResult(Activity fromActivity) { - final Intent i = new ForwardingIntent(fromActivity, AccountSetupBasics.class); - i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NO_ACCOUNTS); - fromActivity.startActivity(i); - } - - /** - * This generates setup data that can be used to start a self-contained account creation flow - * for exchange accounts. - */ - public static Intent actionGetCreateAccountIntent(Context context, String accountManagerType) { - final Intent i = new Intent(context, AccountSetupBasics.class); - i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_ACCOUNT_MANAGER); - i.putExtra(EXTRA_FLOW_ACCOUNT_TYPE, accountManagerType); - return i; - } - - public static void actionAccountCreateFinishedAccountFlow(Activity fromActivity) { - final Intent i= new ForwardingIntent(fromActivity, AccountSetupBasics.class); - // If we're in the "account flow" (from AccountManager), we want to return to the caller - // (in the settings app) - i.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, - new SetupDataFragment(SetupDataFragment.FLOW_MODE_RETURN_TO_CALLER)); - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - fromActivity.startActivity(i); - } - - public static void actionAccountCreateFinishedWithResult(Activity fromActivity) { - final Intent i= new ForwardingIntent(fromActivity, AccountSetupBasics.class); - // If we're in the "no accounts" flow, we want to return to the caller with a result - i.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, - new SetupDataFragment(SetupDataFragment.FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT)); - i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - fromActivity.startActivity(i); - } - - public static void actionAccountCreateFinished(final Activity fromActivity, Account account) { - final Intent i = new Intent(fromActivity, AccountSetupBasics.class); - // If we're not in the "account flow" (from AccountManager), we want to show the - // message list for the new inbox - i.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, - new SetupDataFragment(SetupDataFragment.FLOW_MODE_RETURN_TO_MESSAGE_LIST, account)); - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - fromActivity.startActivity(i); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Check for forced account creation first, as it comes from an externally-generated - // intent and won't have any SetupData prepared. - final Intent intent = getIntent(); - final String action = intent.getAction(); - - if (INTENT_CREATE_ACCOUNT == null) { - INTENT_CREATE_ACCOUNT = getString(R.string.intent_create_account); - } - if (INTENT_CREATE_ACCOUNT.equals(action)) { - mSetupData = new SetupDataFragment(SetupDataFragment.FLOW_MODE_FORCE_CREATE); - } else { - final int intentFlowMode = - intent.getIntExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_UNSPECIFIED); - if (intentFlowMode != SetupDataFragment.FLOW_MODE_UNSPECIFIED) { - mSetupData = new SetupDataFragment(intentFlowMode, - intent.getStringExtra(EXTRA_FLOW_ACCOUNT_TYPE)); - } - } - - final int flowMode = mSetupData.getFlowMode(); - if (flowMode == SetupDataFragment.FLOW_MODE_RETURN_TO_CALLER) { - // Return to the caller who initiated account creation - finish(); - return; - } else if (flowMode == SetupDataFragment.FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT) { - if (EmailContent.count(this, Account.CONTENT_URI) > 0) { - setResult(RESULT_OK); - } else { - setResult(RESULT_CANCELED); - } - finish(); - return; - } else if (flowMode == SetupDataFragment.FLOW_MODE_RETURN_TO_MESSAGE_LIST) { - final Account account = mSetupData.getAccount(); - if (account != null && account.mId >= 0) { - // Show the message list for the new account - //*** - //Welcome.actionOpenAccountInbox(this, account.mId); - finish(); - return; - } - } - - setContentView(R.layout.account_setup_basics); - - // Configure buttons - mManualButton = UiUtilities.getView(this, R.id.manual_setup); - mNextButton = UiUtilities.getView(this, R.id.next); - mManualButton.setVisibility(View.VISIBLE); - mManualButton.setOnClickListener(this); - mNextButton.setOnClickListener(this); - - mManualButton.setPaintFlags(mManualButton.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - - // Force disabled until validator notifies otherwise - setProceedButtonsEnabled(false); - // Lightweight debounce while Async tasks underway - mNextButtonInhibit = false; - - // Set aside incoming AccountAuthenticatorResponse, if there was any - final AccountAuthenticatorResponse authenticatorResponse = - getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); - mSetupData.setAccountAuthenticatorResponse(authenticatorResponse); - if (authenticatorResponse != null) { - // When this Activity is called as part of account authentification flow, - // we are responsible for eventually reporting the result (success or failure) to - // the account manager. Most exit paths represent an failed or abandoned setup, - // so the default is to report the error. Success will be reported by the code in - // AccountSetupOptions that commits the finally created account. - mSetupData.setReportAccountAuthenticationError(true); - } - - // Handle force account creation immediately (now that fragment is set up) - // This is never allowed in a normal user build and will exit immediately. - if (mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_FORCE_CREATE) { - if (!DEBUG_ALLOW_NON_TEST_HARNESS_CREATION && - !ActivityManager.isRunningInTestHarness()) { - LogUtils.e(Logging.LOG_TAG, - "ERROR: Force account create only allowed while in test harness"); - finish(); - return; - } - final String email = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_EMAIL); - final String user = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_USER); - final String incoming = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_INCOMING); - final String outgoing = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_OUTGOING); - if (TextUtils.isEmpty(email) || TextUtils.isEmpty(user) || - TextUtils.isEmpty(incoming) || TextUtils.isEmpty(outgoing)) { - LogUtils.e(Logging.LOG_TAG, "ERROR: Force account create requires extras EMAIL, " + - "USER, INCOMING, OUTGOING"); - finish(); - return; - } - forceCreateAccount(email, user, incoming, outgoing); - // calls finish - // XXX disabled for now, we don't finish account setup in this activity anymore. - // onCheckSettingsComplete(AccountCheckSettingsFragment.CHECK_SETTINGS_OK, mSetupData); - return; - } - - if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) { - mProvider = (Provider) savedInstanceState.getSerializable(STATE_KEY_PROVIDER); - } - - // Launch a loader to look up the owner name. It should be ready well in advance of - // the time the user clicks next or manual. - getLoaderManager().initLoader(OWNER_NAME_LOADER_ID, null, - new LoaderManager.LoaderCallbacks<Cursor>() { - @Override - public Loader<Cursor> onCreateLoader(final int id, final Bundle args) { - return new CursorLoader(AccountSetupBasics.this, - ContactsContract.Profile.CONTENT_URI, - new String[] {ContactsContract.Profile.DISPLAY_NAME_PRIMARY}, - null, null, null); - } - - @Override - public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) { - if (data != null && data.moveToFirst()) { - mOwnerName = data.getString(data.getColumnIndex( - ContactsContract.Profile.DISPLAY_NAME_PRIMARY)); - } - } - - @Override - public void onLoaderReset(final Loader<Cursor> loader) { - } - }); - } - - @Override - public void onPause() { - super.onPause(); - mPaused = true; - } - - @Override - public void onResume() { - super.onResume(); - mPaused = false; - } - - @Override - public void finish() { - // If the account manager initiated the creation, and success was not reported, - // then we assume that we're giving up (for any reason) - report failure. - if (mSetupData.getReportAccountAuthenticationError()) { - final AccountAuthenticatorResponse authenticatorResponse = - mSetupData.getAccountAuthenticatorResponse(); - if (authenticatorResponse != null) { - authenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); - mSetupData.setAccountAuthenticatorResponse(null); - } - } - super.finish(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (mProvider != null) { - outState.putSerializable(STATE_KEY_PROVIDER, mProvider); - } - } - - /** - * Implements OnClickListener - */ - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.next: - // Simple debounce - just ignore while async checks are underway - if (mNextButtonInhibit) { - return; - } - onNext(); - break; - case R.id.manual_setup: - onManualSetup(false); - break; - } - } - - /** - * Return an existing username if found, or null. This is the result of the Callable (below). - */ - private String getOwnerName() { - return mOwnerName; - } - - /** - * Finish the auto setup process, in some cases after showing a warning dialog. - */ - private void finishAutoSetup() { - - final AccountSetupBasicsFragment basicsFragment = getBasicsFragment(); - final String email = basicsFragment.getEmail(); - - try { - mProvider.expandTemplates(email); - - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - HostAuth.setHostAuthFromString(recvAuth, mProvider.incomingUri); - recvAuth.setLogin(mProvider.incomingUsername, null); - - final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, - recvAuth.mProtocol); - recvAuth.mPort = - ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) ? info.portSsl : info.port; - - final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - HostAuth.setHostAuthFromString(sendAuth, mProvider.outgoingUri); - sendAuth.setLogin(mProvider.outgoingUsername, null); - - // Populate the setup data, assuming that the duplicate account check will succeed - populateSetupData(getOwnerName(), email); - - // Stop here if the login credentials duplicate an existing account - // Launch an Async task to do the work - new DuplicateCheckTask(this, email, true) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } catch (URISyntaxException e) { - /* - * If there is some problem with the URI we give up and go on to manual setup. - * Technically speaking, AutoDiscover is OK here, since the user clicked "Next" - * to get here. This will not happen in practice because we don't expect to - * find any EAS accounts in the providers list. - */ - onManualSetup(true); - } - } - - /** - * Async task that continues the work of finishAutoSetup(). Checks for a duplicate - * account and then either alerts the user, or continues. - */ - private class DuplicateCheckTask extends AsyncTask<Void, Void, String> { - private final Context mContext; - private final String mCheckAddress; - private final boolean mAutoSetup; - - public DuplicateCheckTask(Context context, String checkAddress, - boolean autoSetup) { - mContext = context; - mCheckAddress = checkAddress; - // Prevent additional clicks on the next button during Async lookup - mNextButtonInhibit = true; - mAutoSetup = autoSetup; - } - - @Override - protected String doInBackground(Void... params) { - return Utility.findExistingAccount(mContext, null, mCheckAddress); - } - - @Override - protected void onPostExecute(String duplicateAccountName) { - mNextButtonInhibit = false; - // Exit immediately if the user left before we finished - if (mPaused) return; - // Show duplicate account warning, or proceed. - if (duplicateAccountName != null) { - final DuplicateAccountDialogFragment dialogFragment = - DuplicateAccountDialogFragment.newInstance(duplicateAccountName); - dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG); - } else { - if (mAutoSetup) { - final Intent intent = new Intent(AccountSetupBasics.this, SignInActivity.class); - intent.putExtra(SignInActivity.EXTRA_FLOW_MODE_INITIAL, true); - intent.putExtra(SignInActivity.EXTRA_MANUAL_SETUP, false); - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, mSetupData); - startActivity(intent); - } else { - onManualSetup(true); - } - } - } - - @Override - protected void onCancelled(String s) { - mNextButtonInhibit = false; - LogUtils.d(LogUtils.TAG, "DuplicateCheckTask cancelled (AccountSetupBasics)"); - } - } - - /** - * When "next" button is clicked - */ - private void onNext() { - // Try auto-configuration from XML providers (unless in EAS mode, we can skip it) - final String email = getBasicsFragment().getEmail(); - final String[] emailParts = email.split("@"); - final String domain = emailParts[1].trim(); - mProvider = AccountSettingsUtils.findProviderForDomain(this, domain); - if (mProvider != null) { - mProvider.expandTemplates(email); - if (mProvider.note != null) { - final NoteDialogFragment dialogFragment = - NoteDialogFragment.newInstance(mProvider.note); - dialogFragment.show(getFragmentManager(), NoteDialogFragment.TAG); - } else { - finishAutoSetup(); - } - } else { - // Can't use auto setup (although EAS accounts may still be able to AutoDiscover) - new DuplicateCheckTask(this, email, false) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - /** - * When "manual setup" button is clicked - * - * @param allowAutoDiscover - true if the user clicked 'next' and (if the account is EAS) - * it's OK to use autodiscover. false to prevent autodiscover and go straight to manual setup. - * Ignored for IMAP & POP accounts. - */ - private void onManualSetup(boolean allowAutoDiscover) { - final AccountSetupBasicsFragment basicsFragment = getBasicsFragment(); - final String email = basicsFragment.getEmail(); - final String[] emailParts = email.split("@"); - final String user = emailParts[0].trim(); - final String domain = emailParts[1].trim(); - - // Alternate entry to the debug options screen (for devices without a physical keyboard: - // Username: d@d.d - // Password: debug - if (ENTER_DEBUG_SCREEN) { - if ("d@d.d".equals(email)) { - basicsFragment.setEmail(""); - AccountSettings.actionSettingsWithDebug(this); - return; - } - } - - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - recvAuth.setConnection(null, domain, HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE); - recvAuth.setLogin(user, null); - - final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - sendAuth.setConnection(null, domain, HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE); - sendAuth.setLogin(user, null); - - populateSetupData(getOwnerName(), email); - - mSetupData.setAllowAutodiscover(allowAutoDiscover); - - // FLAG: We should not launch the protocol picker if we are coming from device settings, - // (as opposed to in-app adding account.) If we come from device settings, the user has - // already explicitly chosen the account type. - AccountSetupType.actionSelectAccountType(this, mSetupData); - } - - /** - * To support continuous testing, we allow the forced creation of accounts. - * This works in a manner fairly similar to automatic setup, in which the complete server - * Uri's are available, except that we will also skip checking (as if both checks were true) - * and all other UI. - * - * @param email The email address for the new account - * @param user The user name for the new account - * @param incoming The URI-style string defining the incoming account - * @param outgoing The URI-style string defining the outgoing account - */ - private void forceCreateAccount(String email, String user, String incoming, String outgoing) { - Account account = mSetupData.getAccount(); - try { - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - HostAuth.setHostAuthFromString(recvAuth, incoming); - - final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - HostAuth.setHostAuthFromString(sendAuth, outgoing); - - populateSetupData(user, email); - } catch (URISyntaxException e) { - // If we can't set up the URL, don't continue - account setup pages will fail too - Toast.makeText( - this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG).show(); - } - } - - public static void setDefaultsForProtocol(Context context, Account account) { - final String protocol = account.mHostAuthRecv.mProtocol; - if (protocol == null) return; - final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol); - account.mSyncInterval = info.defaultSyncInterval; - account.mSyncLookback = info.defaultLookback; - if (info.offerLocalDeletes) { - account.setDeletePolicy(info.defaultLocalDeletes); - } - } - - /** - * Populate SetupData's account with complete setup info. - */ - private void populateSetupData(String senderName, String senderEmail) { - final Account account = mSetupData.getAccount(); - account.setSenderName(senderName); - account.setEmailAddress(senderEmail); - account.setDisplayName(senderEmail); - setDefaultsForProtocol(this, account); - } - - public void setProceedButtonsEnabled(boolean enabled) { - mManualButton.setEnabled(enabled); - mNextButton.setEnabled(enabled); - } - - /** - * Dialog fragment to show "setup note" dialog - */ - public static class NoteDialogFragment extends DialogFragment { - final static String TAG = "NoteDialogFragment"; - - // Argument bundle keys - private final static String BUNDLE_KEY_NOTE = "NoteDialogFragment.Note"; - - // Public no-args constructor needed for fragment re-instantiation - public NoteDialogFragment() {} - - /** - * Create the dialog with parameters - */ - public static NoteDialogFragment newInstance(String note) { - final NoteDialogFragment f = new NoteDialogFragment(); - final Bundle b = new Bundle(1); - b.putString(BUNDLE_KEY_NOTE, note); - f.setArguments(b); - return f; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final String note = getArguments().getString(BUNDLE_KEY_NOTE); - - return new AlertDialog.Builder(context) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setTitle(android.R.string.dialog_alert_title) - .setMessage(note) - .setPositiveButton( - R.string.okay_action, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Activity a = getActivity(); - if (a instanceof AccountSetupBasics) { - ((AccountSetupBasics)a).finishAutoSetup(); - } - dismiss(); - } - }) - .setNegativeButton( - context.getString(R.string.cancel_action), - null) - .create(); - } - } - - private AccountSetupBasicsFragment getBasicsFragment() { - return (AccountSetupBasicsFragment) - getFragmentManager().findFragmentById(R.id.basics_fragment); - } -} diff --git a/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java b/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java index fd793f2dd..23d892ccb 100644 --- a/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupBasicsFragment.java @@ -16,7 +16,6 @@ package com.android.email.activity.setup; -import android.app.Fragment; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; @@ -30,17 +29,29 @@ import com.android.email.R; import com.android.email.activity.UiUtilities; import com.android.emailcommon.mail.Address; -public class AccountSetupBasicsFragment extends Fragment { +public class AccountSetupBasicsFragment extends AccountSetupFragment implements + View.OnClickListener { private EditText mEmailView; + public interface Callback extends AccountSetupFragment.Callback { + void onBasicsManualSetupButton(); + } + + public static AccountSetupBasicsFragment newInstance() { + return new AccountSetupBasicsFragment(); + } + public AccountSetupBasicsFragment() {} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.account_setup_basics_fragment, container, false); + final View view = inflater.inflate(R.layout.account_setup_basics_fragment, container, + false); mEmailView = UiUtilities.getView(view, R.id.account_email); + final View manualSetupButton = UiUtilities.getView(view, R.id.manual_setup); + manualSetupButton.setOnClickListener(this); final TextWatcher textWatcher = new TextWatcher() { @Override @@ -60,8 +71,13 @@ public class AccountSetupBasicsFragment extends Fragment { return view; } + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + validateFields(); + } + private void validateFields() { - final AccountSetupBasics activity = (AccountSetupBasics) getActivity(); final String emailField = getEmail(); final Address[] addresses = Address.parse(emailField); @@ -69,7 +85,16 @@ public class AccountSetupBasicsFragment extends Fragment { && addresses.length == 1 && !TextUtils.isEmpty(addresses[0].getAddress()); - activity.setProceedButtonsEnabled(emailValid); + final Callback callback = (Callback) getActivity(); + callback.setNextButtonEnabled(emailValid); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.manual_setup) { + Callback callback = (Callback) getActivity(); + callback.onBasicsManualSetupButton(); + } } public void setEmail(final String email) { diff --git a/src/com/android/email/activity/setup/AccountSetupCredentialsFragment.java b/src/com/android/email/activity/setup/AccountSetupCredentialsFragment.java new file mode 100644 index 000000000..4955fed8d --- /dev/null +++ b/src/com/android/email/activity/setup/AccountSetupCredentialsFragment.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 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 android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.android.email.R; +import com.android.email.activity.UiUtilities; +import com.android.email.service.EmailServiceUtils; +import com.android.email.service.EmailServiceUtils.EmailServiceInfo; +import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; +import com.android.emailcommon.provider.Credential; +import com.android.emailcommon.provider.HostAuth; +import com.android.mail.utils.LogUtils; + +import java.util.List; + +public class AccountSetupCredentialsFragment extends AccountSetupFragment + implements OnClickListener { + private static final String EXTRA_EMAIL = "email"; + private static final String EXTRA_PROTOCOL = "protocol"; + + public static final String EXTRA_PASSWORD = "password"; + public static final String EXTRA_OAUTH_PROVIDER = "provider"; + public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken"; + public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken"; + public static final String EXTRA_OAUTH_EXPIRES_IN_SECONDS = "expiresInSeconds"; + + private View mOAuthGroup; + private View mOAuthButton; + private EditText mImapPasswordText; + private EditText mRegularPasswordText; + private TextWatcher mValidationTextWatcher; + private String mEmailAddress; + private boolean mOfferOAuth; + private String mProviderId; + private Context mAppContext; + private Bundle mResults; + + public interface Callback extends AccountSetupFragment.Callback { + void onCredentialsComplete(Bundle results); + } + + /** + * Create a new instance of this fragment with the appropriate email and protocol + * @param email login address for OAuth purposes + * @param protocol protocol of the service we're gathering credentials for + * @return new fragment instance + */ + public static AccountSetupCredentialsFragment newInstance(final String email, + final String protocol) { + final AccountSetupCredentialsFragment f = new AccountSetupCredentialsFragment(); + final Bundle b = new Bundle(2); + b.putString(EXTRA_EMAIL, email); + b.putString(EXTRA_PROTOCOL, protocol); + f.setArguments(b); + return f; + } + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.account_setup_credentials_fragment, container, + false); + + mImapPasswordText = UiUtilities.getView(view, R.id.imap_password); + mRegularPasswordText = UiUtilities.getView(view, R.id.regular_password); + mOAuthGroup = UiUtilities.getView(view, R.id.oauth_group); + mOAuthButton = UiUtilities.getView(view, R.id.sign_in_with_google); + mOAuthButton.setOnClickListener(this); + + // After any text edits, call validateFields() which enables or disables the Next button + mValidationTextWatcher = new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + validatePassword(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { } + }; + mImapPasswordText.addTextChangedListener(mValidationTextWatcher); + mRegularPasswordText.addTextChangedListener(mValidationTextWatcher); + + return view; + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mAppContext = getActivity().getApplicationContext(); + mEmailAddress = getArguments().getString(EXTRA_EMAIL); + final String protocol = getArguments().getString(EXTRA_PROTOCOL); + if (protocol != null) { + final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mAppContext, protocol); + mOfferOAuth = info.offerOAuth; + } else { + // TODO: for now, we might not know what protocol we're using, so just default to + // offering oauth + mOfferOAuth = true; + } + if (mOfferOAuth) { + mOAuthGroup.setVisibility(View.VISIBLE); + mRegularPasswordText.setVisibility(View.GONE); + } else { + mOAuthGroup.setVisibility(View.GONE); + mRegularPasswordText.setVisibility(View.VISIBLE); + } + validatePassword(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mImapPasswordText.removeTextChangedListener(mValidationTextWatcher); + mImapPasswordText = null; + mRegularPasswordText.removeTextChangedListener(mValidationTextWatcher); + mRegularPasswordText = null; + } + + public void validatePassword() { + final Callback callback = (Callback) getActivity(); + if (callback != null) { + callback.setNextButtonEnabled(!TextUtils.isEmpty(getPassword())); + } + // Warn (but don't prevent) if password has leading/trailing spaces + AccountSettingsUtils.checkPasswordSpaces(mAppContext, mImapPasswordText); + AccountSettingsUtils.checkPasswordSpaces(mAppContext, mRegularPasswordText); + } + + @Override + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + if (requestCode == OAuthAuthenticationActivity.REQUEST_OAUTH) { + if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_SUCCESS) { + final String accessToken = data.getStringExtra( + OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN); + final String refreshToken = data.getStringExtra( + OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN); + final int expiresInSeconds = data.getIntExtra( + OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0); + final Bundle results = new Bundle(4); + results.putString(EXTRA_OAUTH_PROVIDER, mProviderId); + results.putString(EXTRA_OAUTH_ACCESS_TOKEN, accessToken); + results.putString(EXTRA_OAUTH_REFRESH_TOKEN, refreshToken); + results.putInt(EXTRA_OAUTH_EXPIRES_IN_SECONDS, expiresInSeconds); + mResults = results; + final Callback callback = (Callback) getActivity(); + callback.onCredentialsComplete(results); + } else if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_FAILURE + || resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_USER_CANCELED) { + LogUtils.i(LogUtils.TAG, "Result from oauth %d", resultCode); + } else { + LogUtils.wtf(LogUtils.TAG, "Unknown result code from OAUTH: %d", resultCode); + } + } else { + LogUtils.e(LogUtils.TAG, "Unknown request code for onActivityResult in" + + " AccountSetupBasics: %d", requestCode); + } + } + + @Override + public void onClick(final View view) { + if (view == mOAuthButton) { + List<OAuthProvider> oauthProviders = AccountSettingsUtils.getAllOAuthProviders( + mAppContext); + // TODO currently the only oauth provider we support is google. + // If we ever have more than 1 oauth provider, then we need to implement some sort + // of picker UI. For now, just always take the first oauth provider. + if (oauthProviders.size() > 0) { + mProviderId = oauthProviders.get(0).id; + final Intent i = new Intent(getActivity(), OAuthAuthenticationActivity.class); + i.putExtra(OAuthAuthenticationActivity.EXTRA_EMAIL_ADDRESS, mEmailAddress); + i.putExtra(OAuthAuthenticationActivity.EXTRA_PROVIDER, mProviderId); + startActivityForResult(i, OAuthAuthenticationActivity.REQUEST_OAUTH); + } + } + } + + public String getPassword() { + if (mOfferOAuth) { + return mImapPasswordText.getText().toString(); + } else { + return mRegularPasswordText.getText().toString(); + } + } + + public Bundle getCredentialResults() { + if (mResults != null) { + return mResults; + } + + final Bundle results = new Bundle(1); + results.putString(EXTRA_PASSWORD, getPassword()); + return results; + } + + public static void populateHostAuthWithResults(final Context context, final HostAuth hostAuth, + final Bundle results) { + if (results == null) { + return; + } + final String password = results.getString(AccountSetupCredentialsFragment.EXTRA_PASSWORD); + if (!TextUtils.isEmpty(password)) { + hostAuth.mPassword = password; + hostAuth.removeCredential(); + } else { + Credential cred = hostAuth.getOrCreateCredential(context); + cred.mProviderId = results.getString( + AccountSetupCredentialsFragment.EXTRA_OAUTH_PROVIDER); + cred.mAccessToken = results.getString( + AccountSetupCredentialsFragment.EXTRA_OAUTH_ACCESS_TOKEN); + cred.mRefreshToken = results.getString( + AccountSetupCredentialsFragment.EXTRA_OAUTH_REFRESH_TOKEN); + cred.mExpiration = System.currentTimeMillis() + + results.getInt( + AccountSetupCredentialsFragment.EXTRA_OAUTH_EXPIRES_IN_SECONDS, 0) + * DateUtils.SECOND_IN_MILLIS; + hostAuth.mPassword = null; + } + } +} diff --git a/src/com/android/email/activity/setup/AccountSetupFinal.java b/src/com/android/email/activity/setup/AccountSetupFinal.java index 04bc80893..68a9b435a 100644 --- a/src/com/android/email/activity/setup/AccountSetupFinal.java +++ b/src/com/android/email/activity/setup/AccountSetupFinal.java @@ -18,7 +18,7 @@ package com.android.email.activity.setup; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; -import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -26,66 +26,227 @@ import android.app.Fragment; import android.app.FragmentTransaction; import android.app.LoaderManager; import android.app.ProgressDialog; -import android.content.ContentValues; import android.content.Context; +import android.content.CursorLoader; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; +import android.database.Cursor; import android.os.Bundle; +import android.provider.ContactsContract; import android.text.TextUtils; import android.view.View; -import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; +import android.widget.Toast; import com.android.email.R; import com.android.email.activity.UiUtilities; -import com.android.email.provider.AccountBackupRestore; import com.android.email.service.EmailServiceUtils; +import com.android.emailcommon.VendorPolicyLoader; import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent; -import com.android.mail.ui.MailAsyncTaskLoader; +import com.android.emailcommon.provider.HostAuth; import com.android.mail.utils.LogUtils; +import com.google.common.annotations.VisibleForTesting; -public class AccountSetupFinal extends AccountSetupActivity implements View.OnClickListener { - private static final String SAVESTATE_IS_PROCESSING_KEY = - "com.android.email.AccountSetupFinal.is_processing"; - private static final String SAVESTATE_STATE = "com.android.email.AccountSetupFinal.state"; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; - private static final String ACCOUNT_CREATION_FRAGMENT_TAG = "AccountCreationFragment"; - private static final String ACCOUNT_FINALIZE_FRAGMENT_TAG = "AccountFinalizeFragment"; +public class AccountSetupFinal extends AccountSetupActivity + implements View.OnClickListener, AccountFinalizeFragment.Callback, + AccountSetupNoteDialogFragment.Callback, AccountCreationFragment.Callback, + AccountCheckSettingsFragment.Callback, SecurityRequiredDialogFragment.Callback, + CheckSettingsErrorDialogFragment.Callback, CheckSettingsProgressDialogFragment.Callback, + AccountSetupTypeFragment.Callback, AccountSetupNamesFragment.Callback, + AccountSetupOptionsFragment.Callback, AccountSetupBasicsFragment.Callback, + AccountServerBaseFragment.Callback, AccountSetupCredentialsFragment.Callback { - private static final String CREATE_ACCOUNT_DIALOG_TAG = "CreateAccountDialog"; + // STOPSHIP: Set to false before shipping, logs PII + private final static boolean ENTER_DEBUG_SCREEN = true; + + /** + * Direct access for forcing account creation + * For use by continuous automated test system (e.g. in conjunction with monkey tests) + * + * === Support for automated testing == + * This activity can also be launched directly via INTENT_CREATE_ACCOUNT. This is intended + * only for use by continuous test systems, and is currently only available when + * {@link ActivityManager#isRunningInTestHarness()} is set. To use this mode, you must + * construct an intent which contains all necessary information to create the account. No + * connection checking is done, so the account may or may not actually work. Here is a sample + * command, for a gmail account "test_account" with a password of "test_password". + * + * $ adb shell am start -a com.android.email.CREATE_ACCOUNT \ + * -e EMAIL test_account@gmail.com \ + * -e USER "Test Account Name" \ + * -e INCOMING imap+ssl+://test_account:test_password@imap.gmail.com \ + * -e OUTGOING smtp+ssl+://test_account:test_password@smtp.gmail.com + * + * Note: For accounts that require the full email address in the login, encode the @ as %40. + * Note: Exchange accounts that require device security policies cannot be created + * automatically. + */ + private static String INTENT_CREATE_ACCOUNT; + private static final String EXTRA_FLOW_MODE = "FLOW_MODE"; + private static final String EXTRA_FLOW_ACCOUNT_TYPE = "FLOW_ACCOUNT_TYPE"; + private static final String EXTRA_CREATE_ACCOUNT_EMAIL = "EMAIL"; + private static final String EXTRA_CREATE_ACCOUNT_USER = "USER"; + private static final String EXTRA_CREATE_ACCOUNT_INCOMING = "INCOMING"; + private static final String EXTRA_CREATE_ACCOUNT_OUTGOING = "OUTGOING"; + private static final Boolean DEBUG_ALLOW_NON_TEST_HARNESS_CREATION = false; + + protected static final String ACTION_JUMP_TO_INCOMING = "jumpToIncoming"; + protected static final String ACTION_JUMP_TO_OUTGOING = "jumpToOutgoing"; + protected static final String ACTION_JUMP_TO_OPTIONS = "jumpToOptions"; + + private static final String SAVESTATE_KEY_IS_PROCESSING = "AccountSetupFinal.is_processing"; + private static final String SAVESTATE_KEY_STATE = "AccountSetupFinal.state"; + private static final String SAVESTATE_KEY_PROVIDER = "AccountSetupFinal.provider"; + private static final String SAVESTATE_KEY_AUTHENTICATOR_RESPONSE = "AccountSetupFinal.authResp"; + private static final String SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR = + "AccountSetupFinal.authErr"; + private static final String SAVESTATE_KEY_IS_PRE_CONFIGURED = "AccountSetupFinal.preconfig"; + private static final String SAVESTATE_KEY_SKIP_AUTO_DISCOVER = "AccountSetupFinal.noAuto"; private static final String CONTENT_FRAGMENT_TAG = "AccountSetupContentFragment"; - private static final int STATE_OPTIONS = 0; - private static final int STATE_NAMES = 1; - private int mState = STATE_OPTIONS; + // Collecting initial email and password + private static final int STATE_BASICS = 0; + // Collect initial password or oauth token + private static final int STATE_CREDENTIALS = 1; + // Show the user some interstitial message after credential entry + private static final int STATE_CREDENTIALS_POST = 2; + // User chose the "Manual Setup" button + private static final int STATE_DIVERT_TO_MANUAL = 3; + // Account is a pre-configured account, run the checker + private static final int STATE_CHECKING_PRECONFIGURED = 4; + // Account is not pre-configured, query user for account type + private static final int STATE_TYPE = 5; + // Auto-discovering exchange account info, possibly other protocols later + private static final int STATE_AUTO_DISCOVER = 6; + // User is entering incoming settings + private static final int STATE_MANUAL_INCOMING = 7; + // We're checking incoming settings + private static final int STATE_CHECKING_INCOMING = 8; + // User is entering outgoing settings + private static final int STATE_MANUAL_OUTGOING = 9; + // We're checking outgoing settings + private static final int STATE_CHECKING_OUTGOING = 10; + // User is entering sync options + private static final int STATE_OPTIONS = 11; + // We're creating the account + private static final int STATE_CREATING = 12; + // User is entering account name and real name + private static final int STATE_NAMES = 13; + // we're finalizing the account + private static final int STATE_FINALIZE = 14; + + private int mState = STATE_BASICS; private boolean mIsProcessing = false; private boolean mForceCreate = false; + private boolean mReportAccountAuthenticatorError; + private AccountAuthenticatorResponse mAccountAuthenticatorResponse; + // True if this provider is found in our providers.xml, set after Basics + private boolean mIsPreConfiguredProvider = false; + // True if the user selected manual setup + private boolean mSkipAutoDiscover = false; + // True if validating the pre-configured provider failed and we want manual setup + private boolean mPreConfiguredFailed = false; + + private VendorPolicyLoader.Provider mProvider; + + // UI elements + @VisibleForTesting + protected ImageButton mNextButton; + + private static final int OWNER_NAME_LOADER_ID = 0; + private String mOwnerName; + + private static final int EXISTING_ACCOUNTS_LOADER_ID = 1; + private Map<String, String> mExistingAccountsMap; + + public static Intent actionNewAccountIntent(final Context context) { + final Intent i = new Intent(context, AccountSetupFinal.class); + i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NORMAL); + return i; + } - private ImageButton mNextButton; + public static Intent actionNewAccountWithResultIntent(final Context context) { + final Intent i = new ForwardingIntent(context, AccountSetupFinal.class); + i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NO_ACCOUNTS); + return i; + } - public static void actionFinal(Activity fromActivity, SetupDataFragment setupData) { - final Intent intent = new ForwardingIntent(fromActivity, AccountSetupFinal.class); - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, setupData); - fromActivity.startActivity(intent); + public static Intent actionGetCreateAccountIntent(final Context context, + final String accountManagerType) { + final Intent i = new Intent(context, AccountSetupFinal.class); + i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_ACCOUNT_MANAGER); + i.putExtra(EXTRA_FLOW_ACCOUNT_TYPE, accountManagerType); + return i; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Intent intent = getIntent(); + final String action = intent.getAction(); + + if (INTENT_CREATE_ACCOUNT == null) { + INTENT_CREATE_ACCOUNT = getString(R.string.intent_create_account); + } + setContentView(R.layout.account_setup); if (savedInstanceState != null) { - mIsProcessing = savedInstanceState.getBoolean(SAVESTATE_IS_PROCESSING_KEY, false); - mState = savedInstanceState.getInt(SAVESTATE_STATE, STATE_OPTIONS); + mIsProcessing = savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PROCESSING, false); + mState = savedInstanceState.getInt(SAVESTATE_KEY_STATE, STATE_OPTIONS); + mProvider = (VendorPolicyLoader.Provider) + savedInstanceState.getSerializable(SAVESTATE_KEY_PROVIDER); + mAccountAuthenticatorResponse = + savedInstanceState.getParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE); + mReportAccountAuthenticatorError = + savedInstanceState.getBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR); + mIsPreConfiguredProvider = + savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED); + mSkipAutoDiscover = savedInstanceState.getBoolean(SAVESTATE_KEY_SKIP_AUTO_DISCOVER); } else { // If we're not restoring from a previous state, we want to configure the initial screen - mState = STATE_OPTIONS; + + // Set aside incoming AccountAuthenticatorResponse, if there was any + mAccountAuthenticatorResponse = getIntent() + .getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); + if (mAccountAuthenticatorResponse != null) { + // When this Activity is called as part of account authentification flow, + // we are responsible for eventually reporting the result (success or failure) to + // the account manager. Most exit paths represent an failed or abandoned setup, + // so the default is to report the error. Success will be reported by the code in + // AccountSetupOptions that commits the finally created account. + mReportAccountAuthenticatorError = true; + } + + // Initialize the SetupDataFragment + if (INTENT_CREATE_ACCOUNT.equals(action)) { + mSetupData.setFlowMode(SetupDataFragment.FLOW_MODE_FORCE_CREATE); + } else { + final int intentFlowMode = intent.getIntExtra(EXTRA_FLOW_MODE, + SetupDataFragment.FLOW_MODE_UNSPECIFIED); + // TODO: do something with this + final String flowAccountType = intent.getStringExtra(EXTRA_FLOW_ACCOUNT_TYPE); + mSetupData.setFlowMode(intentFlowMode); + } + + mState = STATE_BASICS; + // Support unit testing individual screens + if (TextUtils.equals(ACTION_JUMP_TO_INCOMING, action)) { + mState = STATE_MANUAL_INCOMING; + } else if (TextUtils.equals(ACTION_JUMP_TO_OUTGOING, action)) { + mState = STATE_MANUAL_OUTGOING; + } else if (TextUtils.equals(ACTION_JUMP_TO_OPTIONS, action)) { + mState = STATE_OPTIONS; + } updateHeadline(); updateContentFragment(false /* addToBackstack */); } @@ -96,28 +257,143 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl if (!mIsProcessing && mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_FORCE_CREATE) { - // If we are just visiting here to fill in details, exit immediately getFragmentManager().executePendingTransactions(); + + if (!DEBUG_ALLOW_NON_TEST_HARNESS_CREATION && + !ActivityManager.isRunningInTestHarness()) { + LogUtils.e(LogUtils.TAG, + "ERROR: Force account create only allowed while in test harness"); + finish(); + return; + } + mForceCreate = true; } + + // Launch a loader to look up the owner name. It should be ready well in advance of + // the time the user clicks next or manual. + getLoaderManager().initLoader(OWNER_NAME_LOADER_ID, null, + new LoaderManager.LoaderCallbacks<Cursor>() { + @Override + public Loader<Cursor> onCreateLoader(final int id, final Bundle args) { + return new CursorLoader(AccountSetupFinal.this, + ContactsContract.Profile.CONTENT_URI, + new String[] {ContactsContract.Profile.DISPLAY_NAME_PRIMARY}, + null, null, null); + } + + @Override + public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) { + if (data != null && data.moveToFirst()) { + mOwnerName = data.getString(data.getColumnIndex( + ContactsContract.Profile.DISPLAY_NAME_PRIMARY)); + } + } + + @Override + public void onLoaderReset(final Loader<Cursor> loader) {} + }); + + // Launch a loader to cache some info about existing accounts so we can dupe-check against + // them. + getLoaderManager().initLoader(EXISTING_ACCOUNTS_LOADER_ID, null, + new LoaderManager.LoaderCallbacks<Cursor> () { + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + return new CursorLoader(AccountSetupFinal.this, Account.CONTENT_URI, + new String[] {Account.EMAIL_ADDRESS, Account.DISPLAY_NAME}, + null, null, null); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + if (data == null) { + mExistingAccountsMap = null; + return; + } + + mExistingAccountsMap = new HashMap<String, String>(); + + final int emailColumnIndex = data.getColumnIndex(Account.EMAIL_ADDRESS); + final int displayNameColumnIndex = + data.getColumnIndex(Account.DISPLAY_NAME); + + while (data.moveToNext()) { + final String email = data.getString(emailColumnIndex); + final String displayName = data.getString(displayNameColumnIndex); + mExistingAccountsMap.put(email, + TextUtils.isEmpty(displayName) ? email : displayName); + } + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mExistingAccountsMap = null; + } + }); } @Override protected void onResume() { super.onResume(); if (mForceCreate) { - mForceCreate = false; - // We need to do this after onCreate so that we can ensure that the fragment is fully - // created before querying it. - initiateAccountCreation(); + try { + mForceCreate = false; + + /** + * To support continuous testing, we allow the forced creation of accounts. + * This works in a manner fairly similar to automatic setup, in which the complete + * server Uri's are available, except that we will also skip checking (as if both + * checks were true) and all other UI. + * + * email: The email address for the new account + * user: The user name for the new account + * incoming: The URI-style string defining the incoming account + * outgoing: The URI-style string defining the outgoing account + */ + final Intent intent = getIntent(); + final String email = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_EMAIL); + final String user = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_USER); + final String incoming = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_INCOMING); + final String outgoing = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_OUTGOING); + if (TextUtils.isEmpty(email) || TextUtils.isEmpty(user) || + TextUtils.isEmpty(incoming) || TextUtils.isEmpty(outgoing)) { + LogUtils.e(LogUtils.TAG, "ERROR: Force account create requires extras EMAIL, " + + "USER, INCOMING, OUTGOING"); + finish(); + return; + } + + final Account account = mSetupData.getAccount(); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); + recvAuth.setHostAuthFromString(incoming); + + final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); + sendAuth.setHostAuthFromString(outgoing); + + populateSetupData(user, email); + + // We need to do this after onCreate so that we can ensure that the fragment is + // fully created before querying it. + initiateAccountCreation(); + } catch (URISyntaxException e) { + // If we can't set up the URL, don't continue + Toast.makeText(this, + R.string.account_setup_username_password_toast, Toast.LENGTH_LONG).show(); + } } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(SAVESTATE_IS_PROCESSING_KEY, mIsProcessing); - outState.putInt(SAVESTATE_STATE, mState); + outState.putBoolean(SAVESTATE_KEY_IS_PROCESSING, mIsProcessing); + outState.putInt(SAVESTATE_KEY_STATE, mState); + outState.putSerializable(SAVESTATE_KEY_PROVIDER, mProvider); + outState.putParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE, mAccountAuthenticatorResponse); + outState.putBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR, + mReportAccountAuthenticatorError); + outState.putBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED, mIsPreConfiguredProvider); } /** @@ -126,12 +402,30 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl private void updateHeadline() { TextView headlineView = UiUtilities.getView(this, R.id.headline); switch (mState) { + case STATE_BASICS: + headlineView.setText(R.string.account_setup_basics_headline); + break; + case STATE_CREDENTIALS: + // TODO: real string + headlineView.setText(R.string.sign_in_title); + break; + case STATE_TYPE: + headlineView.setText(R.string.account_setup_account_type_headline); + break; + case STATE_MANUAL_INCOMING: + headlineView.setText(R.string.account_setup_incoming_headline); + break; + case STATE_MANUAL_OUTGOING: + headlineView.setText(R.string.account_setup_outgoing_headline); + break; case STATE_OPTIONS: headlineView.setText(R.string.account_setup_options_headline); break; case STATE_NAMES: headlineView.setText(R.string.account_setup_names_headline); break; + default: + throw new IllegalStateException("Incorrect state" + mState); } } @@ -140,17 +434,34 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl * stack, so only call it once per transition. */ private void updateContentFragment(boolean addToBackstack) { - final Fragment f; + final AccountSetupFragment f; switch (mState) { + case STATE_BASICS: + f = AccountSetupBasicsFragment.newInstance(); + break; + case STATE_CREDENTIALS: + f = AccountSetupCredentialsFragment.newInstance(mSetupData.getEmail(), + mSetupData.getAccount().getOrCreateHostAuthRecv(this).mProtocol); + break; + case STATE_TYPE: + f = AccountSetupTypeFragment.newInstance(); + break; + case STATE_MANUAL_INCOMING: + f = AccountSetupIncomingFragment.newInstance(false); + break; + case STATE_MANUAL_OUTGOING: + f = AccountSetupOutgoingFragment.newInstance(false); + break; case STATE_OPTIONS: - f = new AccountSetupOptionsFragment(); + f = AccountSetupOptionsFragment.newInstance(); break; case STATE_NAMES: - f = new AccountSetupNamesFragment(); + f = AccountSetupNamesFragment.newInstance(); break; default: - throw new IllegalStateException("Unknown state " + mState); + throw new IllegalStateException("Incorrect state " + mState); } + f.setState(mState); final FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.setup_fragment_container, f, CONTENT_FRAGMENT_TAG); if (addToBackstack) { @@ -163,8 +474,8 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl * Retrieve the current content fragment * @return The content fragment or null if it wasn't found for some reason */ - private Fragment getContentFragment() { - return getFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); + private AccountSetupFragment getContentFragment() { + return (AccountSetupFragment) getFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); } /** @@ -175,8 +486,98 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl mIsProcessing = false; setNextButtonEnabled(true); + getFragmentManager().executePendingTransactions(); + switch (mState) { + case STATE_BASICS: + onBasicsComplete(); + mState = STATE_CREDENTIALS; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + break; + case STATE_DIVERT_TO_MANUAL: + mState = STATE_TYPE; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + break; + case STATE_CREDENTIALS: + final boolean advance = collectCredentials(); + if (!advance) { + mState = STATE_CREDENTIALS_POST; + break; + } // else fall through + case STATE_CREDENTIALS_POST: + if (mIsPreConfiguredProvider) { + mState = STATE_CHECKING_PRECONFIGURED; + initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING + | SetupDataFragment.CHECK_OUTGOING); + } else { + mState = STATE_TYPE; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + } + break; + case STATE_CHECKING_PRECONFIGURED: + if (mPreConfiguredFailed) { + mState = STATE_MANUAL_INCOMING; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + } else { + mState = STATE_OPTIONS; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + } + break; + case STATE_TYPE: + // We either got here through "Manual Setup" or because we didn't find the provider + // In the former case, we skip auto-discover + if (mSkipAutoDiscover) { + mState = STATE_MANUAL_INCOMING; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + } else { + mState = STATE_AUTO_DISCOVER; + initiateCheckSettingsFragment(SetupDataFragment.CHECK_AUTODISCOVER); + } + break; + case STATE_AUTO_DISCOVER: + // TODO: figure out if we can skip past manual setup + mState = STATE_MANUAL_INCOMING; + updateHeadline(); + updateContentFragment(true); + break; + case STATE_MANUAL_INCOMING: + onIncomingComplete(); + mState = STATE_CHECKING_INCOMING; + initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING); + break; + case STATE_CHECKING_INCOMING: + final EmailServiceUtils.EmailServiceInfo serviceInfo = + EmailServiceUtils.getServiceInfo(this, + getSetupData().getAccount().getProtocol(this)); + if (serviceInfo.usesSmtp) { + mState = STATE_MANUAL_OUTGOING; + } else { + mState = STATE_OPTIONS; + } + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + break; + case STATE_MANUAL_OUTGOING: + onOutgoingComplete(); + mState = STATE_CHECKING_OUTGOING; + initiateCheckSettingsFragment(SetupDataFragment.CHECK_OUTGOING); + break; + case STATE_CHECKING_OUTGOING: + mState = STATE_OPTIONS; + updateHeadline(); + updateContentFragment(true /* addToBackstack */); + break; case STATE_OPTIONS: + mState = STATE_CREATING; + initiateAccountCreation(); + break; + case STATE_CREATING: mState = STATE_NAMES; updateHeadline(); updateContentFragment(true /* addToBackstack */); @@ -186,7 +587,13 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl } break; case STATE_NAMES: - finishActivity(); + initiateAccountFinalize(); + break; + case STATE_FINALIZE: + finish(); + break; + default: + LogUtils.wtf(LogUtils.TAG, "Unknown state %d", mState); break; } } @@ -200,24 +607,34 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl return; } if (mState == STATE_NAMES) { - finishActivity(); + finish(); } else { super.onBackPressed(); } + // After super.onBackPressed() our fragment should be in place, so query the state we + // installed it for + AccountSetupFragment f = getContentFragment(); + mState = f.getState(); + updateHeadline(); } - private void finishActivity() { - if (mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_NO_ACCOUNTS) { - AccountSetupBasics.actionAccountCreateFinishedWithResult(this); - } else if (mSetupData.getFlowMode() != SetupDataFragment.FLOW_MODE_NORMAL) { - AccountSetupBasics.actionAccountCreateFinishedAccountFlow(this); - } else { - final Account account = mSetupData.getAccount(); - if (account != null) { - AccountSetupBasics.actionAccountCreateFinished(this, account); + @Override + public void setAccount(Account account) { + mSetupData.setAccount(account); + } + + @Override + public void finish() { + // If the account manager initiated the creation, and success was not reported, + // then we assume that we're giving up (for any reason) - report failure. + if (mReportAccountAuthenticatorError) { + if (mAccountAuthenticatorResponse != null) { + mAccountAuthenticatorResponse + .onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); + mAccountAuthenticatorResponse = null; } } - finish(); + super.finish(); } /** @@ -227,17 +644,9 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl public void onClick(View view) { switch (view.getId()) { case R.id.next: - // Debounce touches + // Some states are handled without UI, block double-presses here if (!mIsProcessing) { - switch (mState) { - case STATE_OPTIONS: - initiateAccountCreation(); - break; - case STATE_NAMES: - initiateAccountFinalize(); - break; - } - setNextButtonEnabled(false); + proceed(); } break; case R.id.previous: @@ -250,6 +659,266 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl mNextButton.setEnabled(enabled); } + @Override + public void onBasicsManualSetupButton() { + final AccountSetupBasicsFragment basicsFragment = + (AccountSetupBasicsFragment) getContentFragment(); + final String email = basicsFragment.getEmail(); + mSetupData.setEmail(email); + + // Alternate entry to the debug options screen (for devices without a physical keyboard: + // Username: d@d.d + if (ENTER_DEBUG_SCREEN) { + if ("d@d.d".equals(mSetupData.getEmail())) { + AccountSettings.actionSettingsWithDebug(this); + return; + } + } + mState = STATE_DIVERT_TO_MANUAL; + mSkipAutoDiscover = true; + proceed(); + } + + private void onBasicsComplete() { + final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment(); + final String email = f.getEmail(); + mSetupData.setEmail(email); + } + + @Override + public void onCredentialsComplete(Bundle results) { + proceed(); + } + + /** + * + * @return true to proceed, false to remain on the current screen + */ + private boolean collectCredentials() { + final AccountSetupCredentialsFragment f = (AccountSetupCredentialsFragment) + getContentFragment(); + final Bundle results = f.getCredentialResults(); + mSetupData.setCredentialResults(results); + final String email = mSetupData.getEmail(); + final String[] emailParts = email.split("@"); + final String domain = emailParts[1].trim(); + mProvider = AccountSettingsUtils.findProviderForDomain(this, domain); + if (mProvider != null) { + mProvider.expandTemplates(email); + mIsPreConfiguredProvider = true; + if (mProvider.note != null) { + final AccountSetupNoteDialogFragment dialogFragment = + AccountSetupNoteDialogFragment.newInstance(mProvider.note); + dialogFragment.show(getFragmentManager(), AccountSetupNoteDialogFragment.TAG); + return false; + } else { + return finishAutoSetup(); + } + } else { + // Can't use auto setup (although EAS accounts may still be able to AutoDiscover) + mIsPreConfiguredProvider = false; + final String existingAccountName = mExistingAccountsMap.get(email); + if (!TextUtils.isEmpty(existingAccountName)) { + final DuplicateAccountDialogFragment dialogFragment = + DuplicateAccountDialogFragment.newInstance(existingAccountName); + dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG); + return false; + } else { + populateSetupData(mOwnerName, email); + mSkipAutoDiscover = false; + return true; + } + } + } + + @Override + public void onNoteDialogComplete() { + finishAutoSetup(); + } + + /** + * Finish the auto setup process, in some cases after showing a warning dialog. + * Happens after onBasicsComplete + * @return true to proceed, false to remain on the current screen + */ + private boolean finishAutoSetup() { + final String email = mSetupData.getEmail(); + + try { + mProvider.expandTemplates(email); + + final Account account = mSetupData.getAccount(); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); + recvAuth.setHostAuthFromString(mProvider.incomingUri); + recvAuth.setUserName(mProvider.incomingUsername); + AccountSetupCredentialsFragment.populateHostAuthWithResults(this, recvAuth, + mSetupData.getCredentialResults()); + + final EmailServiceUtils.EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, + recvAuth.mProtocol); + recvAuth.mPort = + ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) ? info.portSsl : info.port; + + final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); + sendAuth.setHostAuthFromString(mProvider.outgoingUri); + sendAuth.setUserName(mProvider.outgoingUsername); + AccountSetupCredentialsFragment.populateHostAuthWithResults(this, sendAuth, + mSetupData.getCredentialResults()); + + // Populate the setup data, assuming that the duplicate account check will succeed + populateSetupData(mOwnerName, email); + + final String duplicateAccountName = + mExistingAccountsMap != null ? mExistingAccountsMap.get(email) : null; + if (duplicateAccountName != null) { + final DuplicateAccountDialogFragment dialogFragment = + DuplicateAccountDialogFragment.newInstance(duplicateAccountName); + dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG); + return false; + } + } catch (URISyntaxException e) { + mSkipAutoDiscover = false; + mPreConfiguredFailed = true; + } + return true; + } + + + /** + * Helper method to fill in some per-protocol defaults + * @param account Account object to fill in + */ + public void setDefaultsForProtocol(Account account) { + final String protocol = account.getOrCreateHostAuthRecv(this).mProtocol; + if (protocol == null) return; + final EmailServiceUtils.EmailServiceInfo info = + EmailServiceUtils.getServiceInfo(this, protocol); + account.mSyncInterval = info.defaultSyncInterval; + account.mSyncLookback = info.defaultLookback; + if (info.offerLocalDeletes) { + account.setDeletePolicy(info.defaultLocalDeletes); + } + } + + /** + * Populate SetupData's account with complete setup info, assumes that the receive auth is + * created and its protocol is set + */ + private void populateSetupData(String senderName, String senderEmail) { + final Account account = mSetupData.getAccount(); + account.setSenderName(senderName); + account.setEmailAddress(senderEmail); + account.setDisplayName(senderEmail); + setDefaultsForProtocol(account); + } + + private void onIncomingComplete() { + AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment(); + f.collectUserInput(); + } + + private void onOutgoingComplete() { + AccountSetupOutgoingFragment f = (AccountSetupOutgoingFragment) getContentFragment(); + f.collectUserInput(); + } + + // This callback method is only applicable to using Incoming/Outgoing fragments in settings mode + @Override + public void onAccountServerUIComplete(int checkMode) {} + + // This callback method is only applicable to using Incoming/Outgoing fragments in settings mode + @Override + public void onAccountServerSaveComplete() {} + + private void initiateCheckSettingsFragment(int checkMode) { + final Fragment f = AccountCheckSettingsFragment.newInstance(checkMode); + final Fragment d = CheckSettingsProgressDialogFragment.newInstance(checkMode); + getFragmentManager().beginTransaction() + .add(f, AccountCheckSettingsFragment.TAG) + .add(d, CheckSettingsProgressDialogFragment.TAG) + .commit(); + } + + @Override + public void onCheckSettingsProgressDialogCancel() { + dismissCheckSettingsFragment(); + } + + private void dismissCheckSettingsFragment() { + final Fragment f = getFragmentManager().findFragmentByTag(AccountCheckSettingsFragment.TAG); + final Fragment d = + getFragmentManager().findFragmentByTag(CheckSettingsProgressDialogFragment.TAG); + getFragmentManager().beginTransaction() + .remove(f) + .remove(d) + .commit(); + } + + @Override + public void onCheckSettingsError(int reason, String message) { + dismissCheckSettingsFragment(); + final DialogFragment f = + CheckSettingsErrorDialogFragment.newInstance(reason, message); + f.show(getFragmentManager(), CheckSettingsErrorDialogFragment.TAG); + } + + @Override + public void onCheckSettingsErrorDialogEditCertificate() { + if (mState == STATE_CHECKING_PRECONFIGURED) { + mPreConfiguredFailed = true; + proceed(); + } + final AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment(); + f.onCertificateRequested(); + } + + @Override + public void onCheckSettingsErrorDialogEditSettings() { + // If we're checking pre-configured, set a flag that we failed and navigate fowards to + // incoming settings + if (mState == STATE_CHECKING_PRECONFIGURED || mState == STATE_AUTO_DISCOVER) { + mPreConfiguredFailed = true; + proceed(); + } + } + + @Override + public void onCheckSettingsComplete() { + dismissCheckSettingsFragment(); + proceed(); + } + + @Override + public void onCheckSettingsAutoDiscoverComplete(int result) { + dismissCheckSettingsFragment(); + proceed(); + } + + @Override + public void onCheckSettingsSecurityRequired(String hostName) { + dismissCheckSettingsFragment(); + final DialogFragment f = SecurityRequiredDialogFragment.newInstance(hostName); + f.show(getFragmentManager(), SecurityRequiredDialogFragment.TAG); + } + + @Override + public void onSecurityRequiredDialogResult(boolean ok) { + if (ok) { + proceed(); + } + } + + @Override + public void onChooseProtocol(String protocol) { + final Account account = mSetupData.getAccount(); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); + recvAuth.setConnection(protocol, recvAuth.mAddress, recvAuth.mPort, recvAuth.mFlags); + + recvAuth.mLogin = recvAuth.mLogin + "@" + recvAuth.mAddress; + setDefaultsForProtocol(account); + proceed(); + } + /** * Ths is called when the user clicks the "done" button. * It collects the data from the UI, updates the setup account record, and launches a fragment @@ -257,6 +926,7 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl */ private void initiateAccountCreation() { mIsProcessing = true; + setNextButtonEnabled(false); final Account account = mSetupData.getAccount(); if (account.mHostAuthRecv == null) { @@ -302,7 +972,7 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl final Fragment f = AccountCreationFragment.newInstance(account, syncEmail, syncCalendar, syncContacts, enableNotifications); final FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.add(f, ACCOUNT_CREATION_FRAGMENT_TAG); + ft.add(f, AccountCreationFragment.TAG); ft.commit(); showCreateAccountDialog(); @@ -312,15 +982,20 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl * Called by the account creation fragment after it has completed. * We do a small amount of work here before moving on to the next state. */ - public void proceedFromAccountCreationFragment() { + public void onAccountCreationFragmentComplete() { destroyAccountCreationFragment(); // If the account manager initiated the creation, and success was not reported, // then we assume that we're giving up (for any reason) - report failure. - final AccountAuthenticatorResponse authenticatorResponse = - mSetupData.getAccountAuthenticatorResponse(); - if (authenticatorResponse != null) { - authenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); - mSetupData.setAccountAuthenticatorResponse(null); + if (mAccountAuthenticatorResponse != null) { + final Account account = getSetupData().getAccount(); + final EmailServiceUtils.EmailServiceInfo info = + EmailServiceUtils.getServiceInfo(this, account.getProtocol(this)); + final Bundle b = new Bundle(2); + b.putString(AccountManager.KEY_ACCOUNT_NAME, account.getEmailAddress()); + b.putString(AccountManager.KEY_ACCOUNT_TYPE, info.accountType); + mAccountAuthenticatorResponse.onResult(b); + mAccountAuthenticatorResponse = null; + mReportAccountAuthenticatorError = false; } proceed(); } @@ -328,19 +1003,24 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl public void destroyAccountCreationFragment() { dismissCreateAccountDialog(); - final Fragment f = getFragmentManager().findFragmentByTag(ACCOUNT_CREATION_FRAGMENT_TAG); + final Fragment f = getFragmentManager().findFragmentByTag(AccountCreationFragment.TAG); if (f == null) { LogUtils.wtf(LogUtils.TAG, "Couldn't find AccountCreationFragment to destroy"); } - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.remove(f); - ft.commit(); + getFragmentManager().beginTransaction() + .remove(f) + .commit(); } public static class CreateAccountDialogFragment extends DialogFragment { + public static final String TAG = "CreateAccountDialogFragment"; public CreateAccountDialogFragment() {} + public static CreateAccountDialogFragment newInstance() { + return new CreateAccountDialogFragment(); + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { /// Show "Creating account..." dialog @@ -352,12 +1032,13 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl } protected void showCreateAccountDialog() { - new CreateAccountDialogFragment().show(getFragmentManager(), CREATE_ACCOUNT_DIALOG_TAG); + CreateAccountDialogFragment.newInstance() + .show(getFragmentManager(), CreateAccountDialogFragment.TAG); } protected void dismissCreateAccountDialog() { final DialogFragment f = (DialogFragment) - getFragmentManager().findFragmentByTag(CREATE_ACCOUNT_DIALOG_TAG); + getFragmentManager().findFragmentByTag(CreateAccountDialogFragment.TAG); if (f != null) { f.dismiss(); } @@ -391,12 +1072,16 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl /** * This is called if MailService.setupAccountManagerAccount() fails for some reason */ - protected void showCreateAccountErrorDialog() { + public void showCreateAccountErrorDialog() { new CreateAccountErrorDialogFragment().show(getFragmentManager(), null); } + /** + * Collect the data from AccountSetupNames and finish up account creation + */ private void initiateAccountFinalize() { mIsProcessing = true; + setNextButtonEnabled(false); AccountSetupNamesFragment fragment = (AccountSetupNamesFragment) getContentFragment(); // Update account object from UI @@ -409,97 +1094,15 @@ public class AccountSetupFinal extends AccountSetupActivity implements View.OnCl final Fragment f = AccountFinalizeFragment.newInstance(account); final FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.add(f, ACCOUNT_FINALIZE_FRAGMENT_TAG); + ft.add(f, AccountFinalizeFragment.TAG); ft.commit(); } - - public static class AccountFinalizeFragment extends Fragment { - private static final String ACCOUNT_TAG = "account"; - - private static final int FINAL_ACCOUNT_TASK_LOADER_ID = 0; - - private Context mAppContext; - - public AccountFinalizeFragment() {} - - public static AccountFinalizeFragment newInstance(Account account) { - final AccountFinalizeFragment f = new AccountFinalizeFragment(); - final Bundle args = new Bundle(1); - args.putParcelable(ACCOUNT_TAG, account); - f.setArguments(args); - return f; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mAppContext = getActivity().getApplicationContext(); - - setRetainInstance(true); - } - - @Override - public void onResume() { - super.onResume(); - - getLoaderManager().initLoader(FINAL_ACCOUNT_TASK_LOADER_ID, getArguments(), - new LoaderManager.LoaderCallbacks<Boolean>() { - @Override - public Loader<Boolean> onCreateLoader(int id, Bundle args) { - final Account accountArg = args.getParcelable(ACCOUNT_TAG); - return new FinalSetupTaskLoader(mAppContext, accountArg); - } - - @Override - public void onLoadFinished(Loader<Boolean> loader, Boolean success) { - if (success && isResumed()) { - AccountSetupFinal activity = (AccountSetupFinal) getActivity(); - activity.finishActivity(); - } - } - - @Override - public void onLoaderReset(Loader<Boolean> loader) { - } - }); - } - - /** - * Final account setup work is handled in this Loader: - * Commit final values to provider - * Trigger account backup - */ - private static class FinalSetupTaskLoader extends MailAsyncTaskLoader<Boolean> { - - private final Account mAccount; - - public FinalSetupTaskLoader(Context context, Account account) { - super(context); - mAccount = account; - } - - Account getAccount() { - return mAccount; - } - - @Override - public Boolean loadInBackground() { - // Update the account in the database - final ContentValues cv = new ContentValues(); - cv.put(EmailContent.AccountColumns.DISPLAY_NAME, mAccount.getDisplayName()); - cv.put(EmailContent.AccountColumns.SENDER_NAME, mAccount.getSenderName()); - mAccount.update(getContext(), cv); - - // Update the backup (side copy) of the accounts - AccountBackupRestore.backup(getContext()); - - return true; - } - - @Override - protected void onDiscardResult(Boolean result) {} - } + /** + * Called when the AccountFinalizeFragment has finished its tasks + */ + @Override + public void onAccountFinalizeFragmentComplete() { + finish(); } } diff --git a/src/com/android/email/activity/setup/AccountSetupFragment.java b/src/com/android/email/activity/setup/AccountSetupFragment.java new file mode 100644 index 000000000..f38e909a1 --- /dev/null +++ b/src/com/android/email/activity/setup/AccountSetupFragment.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 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 android.app.Fragment; + +/** + * Superclass for setup UI fragments. + * Currently holds a super-interface for the callbacks, as well as the state it was launched for so + * we can unwind things correctly when the user navigates the back stack. + */ +public class AccountSetupFragment extends Fragment { + private int mState; + + public interface Callback { + void setNextButtonEnabled(boolean enabled); + } + + public void setState(int state) { + mState = state; + } + + public int getState() { + return mState; + } +} diff --git a/src/com/android/email/activity/setup/AccountSetupIncoming.java b/src/com/android/email/activity/setup/AccountSetupIncoming.java deleted file mode 100644 index 3973f779c..000000000 --- a/src/com/android/email/activity/setup/AccountSetupIncoming.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2008 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 android.app.Activity; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ImageButton; - -import com.android.email.R; -import com.android.email.activity.UiUtilities; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.HostAuth; - -/** - * Provides setup flow for IMAP/POP accounts. - * - * Uses AccountSetupIncomingFragment for primary UI. Uses AccountCheckSettingsFragment to validate - * the settings as entered. If the account is OK, proceeds to AccountSetupOutgoing. - */ -public class AccountSetupIncoming extends AccountSetupActivity - implements AccountSetupIncomingFragment.Callback, OnClickListener { - - /* package */ AccountServerBaseFragment mFragment; - private ImageButton mNextButton; - /* package */ boolean mNextButtonEnabled; - private boolean mStartedAutoDiscovery; - private EmailServiceInfo mServiceInfo; - - // Keys for savedInstanceState - private final static String STATE_STARTED_AUTODISCOVERY = - "AccountSetupExchange.StartedAutoDiscovery"; - - // Extras for AccountSetupIncoming intent - - public static void actionIncomingSettings(Activity fromActivity, SetupDataFragment setupData) { - final Intent intent = new Intent(fromActivity, AccountSetupIncoming.class); - // Add the additional information to the intent, in case the Email process is killed. - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, setupData); - fromActivity.startActivity(intent); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final HostAuth hostAuth = mSetupData.getAccount().mHostAuthRecv; - mServiceInfo = EmailServiceUtils.getServiceInfo(this, hostAuth.mProtocol); - - setContentView(R.layout.account_setup_incoming); - mFragment = (AccountServerBaseFragment) - getFragmentManager().findFragmentById(R.id.setup_fragment); - - // Configure fragment - mFragment.setCallback(this); - - mNextButton = UiUtilities.getView(this, R.id.next); - mNextButton.setOnClickListener(this); - UiUtilities.getView(this, R.id.previous).setOnClickListener(this); - - // One-shot to launch autodiscovery at the entry to this activity (but not if it restarts) - if (mServiceInfo.usesAutodiscover) { - mStartedAutoDiscovery = false; - if (savedInstanceState != null) { - mStartedAutoDiscovery = savedInstanceState.getBoolean(STATE_STARTED_AUTODISCOVERY); - } - if (!mStartedAutoDiscovery) { - startAutoDiscover(); - } - } - - // If we've got a default prefix for this protocol, use it - final String prefix = mServiceInfo.inferPrefix; - if (prefix != null && !hostAuth.mAddress.startsWith(prefix + ".")) { - hostAuth.mAddress = prefix + "." + hostAuth.mAddress; - } - } - - /** - * Implements View.OnClickListener - */ - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.next: - mFragment.onNext(); - break; - case R.id.previous: - onBackPressed(); - break; - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(STATE_STARTED_AUTODISCOVERY, mStartedAutoDiscovery); - } - - /** - * 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: - * Editing existing account - * AutoDiscover blocked (used for unit testing only) - * 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 (!mSetupData.isAllowAutodiscover()) { - return; - } - - final Account account = mSetupData.getAccount(); - // If we've got a username and password and we're NOT editing, try autodiscover - final String username = account.mHostAuthRecv.mLogin; - final String password = account.mHostAuthRecv.mPassword; - if (username != null && password != null) { - onProceedNext(SetupDataFragment.CHECK_AUTODISCOVER, mFragment); - } - } - - public void onAutoDiscoverComplete(int result, SetupDataFragment setupData) { - // If authentication failed, exit immediately (to re-enter credentials) - mSetupData = setupData; - if (result == AccountCheckSettingsFragment.AUTODISCOVER_AUTHENTICATION) { - finish(); - return; - } - - // If data was returned, proceed to next screen - if (result == AccountCheckSettingsFragment.AUTODISCOVER_OK) { - mFragment.onNext(); - } - // Otherwise, proceed into this activity for manual setup - } - - /** - * Implements AccountServerBaseFragment.Callback - * - * Launches the account checker. Positive results are reported to onCheckSettingsOk(). - */ - @Override - public void onProceedNext(int checkMode, AccountServerBaseFragment target) { - AccountCheckSettingsFragment checkerFragment = - AccountCheckSettingsFragment.newInstance(checkMode, target); - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG); - transaction.addToBackStack("back"); - transaction.commit(); - } - - /** - * Implements AccountServerBaseFragment.Callback - */ - @Override - public void onEnableProceedButtons(boolean enable) { - mNextButtonEnabled = enable; - mNextButton.setEnabled(enable); - } - - /** - * Implements AccountServerBaseFragment.Callback - * - * If the checked settings are OK, proceed to outgoing settings screen - */ - @Override - public void onCheckSettingsComplete(int result, SetupDataFragment setupData) { - mSetupData = setupData; - if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { - if (mServiceInfo.usesSmtp) { - AccountSetupOutgoing.actionOutgoingSettings(this, mSetupData); - } else { - AccountSetupFinal.actionFinal(this, mSetupData); - finish(); - } - } - } -} diff --git a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java index 7f6d796ec..637bec376 100644 --- a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java @@ -19,12 +19,12 @@ package com.android.email.activity.setup; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.Loader; import android.net.Uri; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; -import android.text.format.DateUtils; import android.text.method.DigitsKeyListener; import android.view.LayoutInflater; import android.view.View; @@ -44,14 +44,13 @@ import com.android.email.service.EmailServiceUtils; import com.android.email.service.EmailServiceUtils.EmailServiceInfo; import com.android.email.view.CertificateSelector; import com.android.email.view.CertificateSelector.HostCallback; -import com.android.email2.ui.MailActivityEmail; import com.android.emailcommon.Device; -import com.android.emailcommon.Logging; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Credential; import com.android.emailcommon.provider.HostAuth; import com.android.emailcommon.utility.CertificateRequestor; import com.android.emailcommon.utility.Utility; +import com.android.mail.ui.MailAsyncTaskLoader; import com.android.mail.utils.LogUtils; import java.io.IOException; @@ -91,11 +90,16 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment private TextWatcher mValidationTextWatcher; // Support for lifecycle - private boolean mStarted; private boolean mLoaded; private String mCacheLoginCredential; private EmailServiceInfo mServiceInfo; + public static AccountSetupIncomingFragment newInstance(boolean settingsMode) { + final AccountSetupIncomingFragment f = new AccountSetupIncomingFragment(); + f.setArguments(getArgs(settingsMode)); + return f; + } + // Public no-args constructor needed for fragment re-instantiation public AccountSetupIncomingFragment() {} @@ -105,9 +109,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment */ @Override public void onCreate(Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreate"); - } super.onCreate(savedInstanceState); if (savedInstanceState != null) { @@ -119,9 +120,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreateView"); - } final int layoutId = mSettingsMode ? R.layout.account_settings_incoming_fragment : R.layout.account_setup_incoming_fragment; @@ -190,9 +188,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment @Override public void onActivityCreated(Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated"); - } super.onActivityCreated(savedInstanceState); mClientCertificateSelector.setHostCallback(this); @@ -201,8 +196,21 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment (SetupDataFragment.SetupDataContainer) context; mSetupData = container.getSetupData(); final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); - mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mAppContext); + + // Pre-fill info as appropriate + if (!mSetupData.isIncomingCredLoaded()) { + recvAuth.mLogin = mSetupData.getEmail(); + AccountSetupCredentialsFragment.populateHostAuthWithResults(context, recvAuth, + mSetupData.getCredentialResults()); + final String[] emailParts = mSetupData.getEmail().split("@"); + final String domain = emailParts[1]; + recvAuth.setConnection(recvAuth.mProtocol, domain, HostAuth.PORT_UNKNOWN, + HostAuth.FLAG_NONE); + mSetupData.setIncomingCredLoaded(true); + } + + mServiceInfo = EmailServiceUtils.getServiceInfo(mAppContext, recvAuth.mProtocol); if (mServiceInfo.offerLocalDeletes) { SpinnerOption deletePolicies[] = { @@ -244,18 +252,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment context, android.R.layout.simple_spinner_item, securityTypes); securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSecurityTypeView.setAdapter(securityTypesAdapter); - } - /** - * Called when the Fragment is visible to the user. - */ - @Override - public void onStart() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStart"); - } - super.onStart(); - mStarted = true; configureEditor(); loadSettings(); } @@ -265,34 +262,11 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment */ @Override public void onResume() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onResume"); - } super.onResume(); validateFields(); } @Override - public void onPause() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onPause"); - } - super.onPause(); - } - - /** - * Called when the Fragment is no longer started. - */ - @Override - public void onStop() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStop"); - } - super.onStop(); - mStarted = false; - } - - @Override public void onDestroyView() { // Make sure we don't get callbacks after the views are supposed to be destroyed // and also don't hold onto them longer than we need @@ -323,22 +297,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment super.onDestroyView(); } - /** - * Called when the fragment is no longer in use. - */ - @Override - public void onDestroy() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onDestroy"); - } - super.onDestroy(); - } - @Override public void onSaveInstanceState(Bundle outState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onSaveInstanceState"); - } super.onSaveInstanceState(outState); outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); @@ -346,24 +306,12 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment } /** - * Activity provides callbacks here. This also triggers loading and setting up the UX - */ - @Override - public void setCallback(Callback callback) { - super.setCallback(callback); - if (mStarted) { - configureEditor(); - loadSettings(); - } - } - - /** * Configure the editor for the account type */ private void configureEditor() { final Account account = mSetupData.getAccount(); if (account == null || account.mHostAuthRecv == null) { - LogUtils.e(Logging.LOG_TAG, + LogUtils.e(LogUtils.TAG, "null account or host auth. account null: %b host auth null: %b", account == null, account == null || account.mHostAuthRecv == null); return; @@ -392,8 +340,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment if (mLoaded) return; final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); - mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mAppContext); + mServiceInfo = EmailServiceUtils.getServiceInfo(mAppContext, recvAuth.mProtocol); mAuthenticationView.setAuthInfo(mServiceInfo.offerOAuth, recvAuth); if (mAuthenticationLabel != null) { if (mServiceInfo.offerOAuth) { @@ -432,7 +380,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment flags |= HostAuth.FLAG_SSL; } // Strip out any flags that are not related to security type. - int securityTypeFlags = (flags & (HostAuth.FLAG_SSL | HostAuth.FLAG_TLS | HostAuth.FLAG_NONE)); + int securityTypeFlags = (flags & HostAuth.FLAG_TRANSPORTSECURITY_MASK); SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, securityTypeFlags); final String hostname = recvAuth.mAddress; @@ -466,7 +414,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment } private int getPortFromSecurityType(boolean useSsl) { - final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, + final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mAppContext, mSetupData.getAccount().mHostAuthRecv.mProtocol); return useSsl ? info.portSsl : info.port; } @@ -483,7 +431,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment mClientCertificateSelector.setVisibility(mode); String deviceId = ""; try { - deviceId = Device.getDeviceId(mContext); + deviceId = Device.getDeviceId(mAppContext); } catch (IOException e) { // Not required } @@ -500,44 +448,72 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment onUseSslChanged(sslSelected); } + private static class SaveSettingsLoader extends MailAsyncTaskLoader<Boolean> { + private final SetupDataFragment mSetupData; + private final boolean mSettingsMode; + + private SaveSettingsLoader(Context context, SetupDataFragment setupData, + boolean settingsMode) { + super(context); + mSetupData = setupData; + mSettingsMode = settingsMode; + } + + @Override + public Boolean loadInBackground() { + if (mSettingsMode) { + saveSettingsAfterEdit(getContext(), mSetupData); + } else { + saveSettingsAfterSetup(getContext(), mSetupData); + } + return true; + } + + @Override + protected void onDiscardResult(Boolean result) {} + } + + @Override + public Loader<Boolean> getSaveSettingsLoader() { + return new SaveSettingsLoader(mAppContext, mSetupData, mSettingsMode); + } + /** * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. * Note, we update account here (as well as the account.mHostAuthRecv) because we edit * account's delete policy here. * Blocking - do not call from UI Thread. */ - @Override - public void saveSettingsAfterEdit() { - final Account account = mSetupData.getAccount(); - account.update(mContext, account.toContentValues()); + public static void saveSettingsAfterEdit(Context context, SetupDataFragment setupData) { + final Account account = setupData.getAccount(); + account.update(context, account.toContentValues()); final Credential cred = account.mHostAuthRecv.mCredential; if (cred != null) { if (cred.isSaved()) { - cred.update(mContext, cred.toContentValues()); + cred.update(context, cred.toContentValues()); } else { - cred.save(mContext); + cred.save(context); account.mHostAuthRecv.mCredentialKey = cred.mId; } } - account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); + account.mHostAuthRecv.update(context, account.mHostAuthRecv.toContentValues()); // Update the backup (side copy) of the accounts - AccountBackupRestore.backup(mContext); + AccountBackupRestore.backup(context); } /** * Entry point from Activity after entering new settings and verifying them. For setup mode. */ - @Override - public void saveSettingsAfterSetup() { - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); - final HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); + public static void saveSettingsAfterSetup(Context context, SetupDataFragment setupData) { + final Account account = setupData.getAccount(); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(context); + final HostAuth sendAuth = account.getOrCreateHostAuthSend(context); // Set the username and password for the outgoing settings to the username and // password the user just set for incoming. Use the verified host address to try and // pick a smarter outgoing address. final String hostName = - AccountSettingsUtils.inferServerName(mContext, recvAuth.mAddress, null, "smtp"); + AccountSettingsUtils.inferServerName(context, recvAuth.mAddress, null, "smtp"); sendAuth.setLogin(recvAuth.mLogin, recvAuth.mPassword); sendAuth.setConnection(sendAuth.mProtocol, hostName, sendAuth.mPort, sendAuth.mFlags); } @@ -546,7 +522,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment * Entry point from Activity, when "next" button is clicked */ @Override - public void onNext() { + public void collectUserInput() { final Account account = mSetupData.getAccount(); // Make sure delete policy is an valid option before using it; otherwise, the results are @@ -556,9 +532,9 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment (Integer) ((SpinnerOption) mDeletePolicyView.getSelectedItem()).value); } - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); + final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mAppContext); final String userName = mUsernameView.getText().toString().trim(); - final String userPassword = mAuthenticationView.getPassword().toString(); + final String userPassword = mAuthenticationView.getPassword(); recvAuth.setLogin(userName, userPassword); if (!TextUtils.isEmpty(mAuthenticationView.getOAuthProvider())) { Credential cred = recvAuth.getOrCreateCredential(getActivity()); @@ -571,7 +547,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment serverPort = Integer.parseInt(mPortView.getText().toString().trim()); } catch (NumberFormatException e) { serverPort = getPortFromSecurityType(getSslSelected()); - LogUtils.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); + LogUtils.d(LogUtils.TAG, "Non-integer server port; using '" + serverPort + "'"); } final int securityType = (Integer) ((SpinnerOption) mSecurityTypeView.getSelectedItem()).value; @@ -584,8 +560,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment } recvAuth.mClientCertAlias = mClientCertificateSelector.getCertificate(); - mCallback.onProceedNext(SetupDataFragment.CHECK_INCOMING, this); - clearButtonBounce(); + final Callback callback = (Callback) getActivity(); + callback.onAccountServerUIComplete(SetupDataFragment.CHECK_INCOMING); } @Override @@ -604,16 +580,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment return deletePolicyChanged || super.haveSettingsChanged(); } - /** - * Implements AccountCheckSettingsFragment.Callbacks - */ - @Override - public void onAutoDiscoverComplete(int result, SetupDataFragment setupData) { - mSetupData = setupData; - final AccountSetupIncoming activity = (AccountSetupIncoming) getActivity(); - activity.onAutoDiscoverComplete(result, setupData); - } - @Override public void onValidateStateChanged() { validateFields(); @@ -621,10 +587,11 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment @Override public void onRequestSignIn() { - // Launch the signin activity. - // TODO: at some point we should just use the sign in fragment on the main setup activity. - final Intent intent = new Intent(getActivity(), SignInActivity.class); - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, mSetupData); + // Launch the credentials activity. + final String protocol = + mSetupData.getAccount().getOrCreateHostAuthRecv(mAppContext).mProtocol; + final Intent intent = AccountCredentials.getAccountCredentialsIntent(getActivity(), + mUsernameView.getText().toString(), protocol); startActivityForResult(intent, SIGN_IN_REQUEST); } @@ -646,20 +613,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment } else if (requestCode == SIGN_IN_REQUEST && resultCode == Activity.RESULT_OK) { final Account account = mSetupData.getAccount(); final HostAuth recvAuth = account.getOrCreateHostAuthRecv(getActivity()); - final String password = data.getStringExtra(SignInActivity.EXTRA_PASSWORD); - if (!TextUtils.isEmpty(password)) { - recvAuth.mPassword = password; - recvAuth.removeCredential(); - } else { - Credential cred = recvAuth.getOrCreateCredential(getActivity()); - cred.mProviderId = data.getStringExtra(SignInActivity.EXTRA_OAUTH_PROVIDER); - cred.mAccessToken = data.getStringExtra(SignInActivity.EXTRA_OAUTH_ACCESS_TOKEN); - cred.mRefreshToken = data.getStringExtra(SignInActivity.EXTRA_OAUTH_REFRESH_TOKEN); - cred.mExpiration = System.currentTimeMillis() + - data.getIntExtra(SignInActivity.EXTRA_OAUTH_EXPIRES_IN_SECONDS, 0) * - DateUtils.SECOND_IN_MILLIS; - recvAuth.mPassword = null; - } + AccountSetupCredentialsFragment.populateHostAuthWithResults(mAppContext, recvAuth, + data.getExtras()); mAuthenticationView.setAuthInfo(mServiceInfo.offerOAuth, recvAuth); } } diff --git a/src/com/android/email/activity/setup/AccountSetupNamesFragment.java b/src/com/android/email/activity/setup/AccountSetupNamesFragment.java index 393f4c2b9..0e52ad307 100644 --- a/src/com/android/email/activity/setup/AccountSetupNamesFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupNamesFragment.java @@ -16,7 +16,6 @@ package com.android.email.activity.setup; -import android.app.Fragment; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; @@ -38,11 +37,19 @@ import com.android.email.activity.UiUtilities; import com.android.email.service.EmailServiceUtils; import com.android.emailcommon.provider.Account; -public class AccountSetupNamesFragment extends Fragment { +public class AccountSetupNamesFragment extends AccountSetupFragment { private EditText mDescription; private EditText mName; private boolean mRequiresName = true; + public interface Callback extends AccountSetupFragment.Callback { + + } + + public static AccountSetupNamesFragment newInstance() { + return new AccountSetupNamesFragment(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -158,10 +165,10 @@ public class AccountSetupNamesFragment extends Fragment { mName.setError(null); } } - final AccountSetupFinal activity = (AccountSetupFinal) getActivity(); - if (activity != null) { + final Callback callback = (Callback) getActivity(); + if (callback != null) { // If we're not attached to the activity, this state probably doesn't need updating - activity.setNextButtonEnabled(enableNextButton); + callback.setNextButtonEnabled(enableNextButton); } } diff --git a/src/com/android/email/activity/setup/AccountSetupNoteDialogFragment.java b/src/com/android/email/activity/setup/AccountSetupNoteDialogFragment.java new file mode 100644 index 000000000..c889e9978 --- /dev/null +++ b/src/com/android/email/activity/setup/AccountSetupNoteDialogFragment.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 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 android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.email.R; + +public class AccountSetupNoteDialogFragment extends DialogFragment { + public static final String TAG = "NoteDialogFragment"; + + // Argument bundle keys + private static final String BUNDLE_KEY_NOTE = "NoteDialogFragment.Note"; + + public static interface Callback { + void onNoteDialogComplete(); + } + + // Public no-args constructor needed for fragment re-instantiation + public AccountSetupNoteDialogFragment() {} + + /** + * Create the dialog with parameters + */ + public static AccountSetupNoteDialogFragment newInstance(String note) { + final AccountSetupNoteDialogFragment f = new AccountSetupNoteDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(BUNDLE_KEY_NOTE, note); + f.setArguments(b); + return f; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final String note = getArguments().getString(BUNDLE_KEY_NOTE); + + return new AlertDialog.Builder(context) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setTitle(android.R.string.dialog_alert_title) + .setMessage(note) + .setPositiveButton( + R.string.okay_action, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final Callback a = (Callback) getActivity(); + a.onNoteDialogComplete(); + dismiss(); + } + }) + .setNegativeButton( + context.getString(R.string.cancel_action), + null) + .create(); + } +} diff --git a/src/com/android/email/activity/setup/AccountSetupOptionsFragment.java b/src/com/android/email/activity/setup/AccountSetupOptionsFragment.java index b89ab3fd0..272045503 100644 --- a/src/com/android/email/activity/setup/AccountSetupOptionsFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupOptionsFragment.java @@ -16,7 +16,6 @@ package com.android.email.activity.setup; -import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -32,7 +31,7 @@ import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Policy; import com.android.emailcommon.service.SyncWindow; -public class AccountSetupOptionsFragment extends Fragment { +public class AccountSetupOptionsFragment extends AccountSetupFragment { private Spinner mCheckFrequencyView; private Spinner mSyncWindowView; private CheckBox mNotifyView; @@ -45,6 +44,14 @@ public class AccountSetupOptionsFragment extends Fragment { /** Default sync window for new EAS accounts */ private static final int SYNC_WINDOW_EAS_DEFAULT = SyncWindow.SYNC_WINDOW_1_WEEK; + public interface Callback extends AccountSetupFragment.Callback { + + } + + public static AccountSetupOptionsFragment newInstance() { + return new AccountSetupOptionsFragment(); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoing.java b/src/com/android/email/activity/setup/AccountSetupOutgoing.java deleted file mode 100644 index 4d7327420..000000000 --- a/src/com/android/email/activity/setup/AccountSetupOutgoing.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2008 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.R; -import com.android.email.activity.UiUtilities; - -import android.app.Activity; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ImageButton; - -/** - * Provides setup flow for SMTP (for IMAP/POP accounts). - * - * Uses AccountSetupOutgoingFragment for primary UI. Uses AccountCheckSettingsFragment to validate - * the settings as entered. If the account is OK, proceeds to AccountSetupOptions. - */ -public class AccountSetupOutgoing extends AccountSetupActivity - implements AccountSetupOutgoingFragment.Callback, OnClickListener { - - /* package */ AccountSetupOutgoingFragment mFragment; - private ImageButton mNextButton; - /* package */ boolean mNextButtonEnabled; - - public static void actionOutgoingSettings(Activity fromActivity, SetupDataFragment setupData) { - Intent intent = new Intent(fromActivity, AccountSetupOutgoing.class); - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, setupData); - fromActivity.startActivity(intent); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.account_setup_outgoing); - - mFragment = (AccountSetupOutgoingFragment) - getFragmentManager().findFragmentById(R.id.setup_fragment); - - // Configure fragment - mFragment.setCallback(this); - - mNextButton = UiUtilities.getView(this, R.id.next); - mNextButton.setOnClickListener(this); - UiUtilities.getView(this, R.id.previous).setOnClickListener(this); - } - - /** - * Implements View.OnClickListener - */ - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.next: - mFragment.onNext(); - break; - case R.id.previous: - onBackPressed(); - break; - } - } - - /** - * Implements AccountSetupOugoingFragment.Callback - * - * Launches the account checker. Positive results are reported to onCheckSettingsOk(). - */ - @Override - public void onProceedNext(int checkMode, AccountServerBaseFragment target) { - final AccountCheckSettingsFragment checkerFragment = - AccountCheckSettingsFragment.newInstance(checkMode, target); - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG); - transaction.addToBackStack("back"); - transaction.commit(); - } - - /** - * Implements AccountSetupOugoingFragment.Callback - */ - @Override - public void onEnableProceedButtons(boolean enable) { - mNextButtonEnabled = enable; - mNextButton.setEnabled(enable); - } - - /** - * Implements AccountServerBaseFragment.Callback - * - * If the checked settings are OK, proceed to options screen - */ - @Override - public void onCheckSettingsComplete(int result, SetupDataFragment setupData) { - mSetupData = setupData; - if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { - AccountSetupFinal.actionFinal(this, mSetupData); - finish(); - } - } -} diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java index 3583f4fd5..c8e1d0b2a 100644 --- a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java @@ -19,7 +19,7 @@ package com.android.email.activity.setup; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.net.Uri; +import android.content.Loader; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; @@ -41,15 +41,11 @@ import com.android.email.R; import com.android.email.activity.UiUtilities; import com.android.email.activity.setup.AuthenticationView.AuthenticationCallback; import com.android.email.provider.AccountBackupRestore; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.email2.ui.MailActivityEmail; -import com.android.emailcommon.Logging; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Credential; import com.android.emailcommon.provider.HostAuth; -import com.android.emailcommon.utility.CertificateRequestor; import com.android.emailcommon.utility.Utility; +import com.android.mail.ui.MailAsyncTaskLoader; import com.android.mail.utils.LogUtils; /** @@ -77,9 +73,14 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment private Spinner mSecurityTypeView; // Support for lifecycle - private boolean mStarted; private boolean mLoaded; + public static AccountSetupOutgoingFragment newInstance(boolean settingsMode) { + final AccountSetupOutgoingFragment f = new AccountSetupOutgoingFragment(); + f.setArguments(getArgs(settingsMode)); + return f; + } + // Public no-args constructor needed for fragment re-instantiation public AccountSetupOutgoingFragment() {} @@ -89,9 +90,6 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment */ @Override public void onCreate(Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreate"); - } super.onCreate(savedInstanceState); if (savedInstanceState != null) { @@ -103,9 +101,6 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreateView"); - } final int layoutId = mSettingsMode ? R.layout.account_settings_outgoing_fragment : R.layout.account_setup_outgoing_fragment; @@ -192,22 +187,7 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment @Override public void onActivityCreated(Bundle savedInstanceState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onActivityCreated"); - } super.onActivityCreated(savedInstanceState); - } - - /** - * Called when the Fragment is visible to the user. - */ - @Override - public void onStart() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStart"); - } - super.onStart(); - mStarted = true; loadSettings(); } @@ -216,72 +196,34 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment */ @Override public void onResume() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onResume"); - } super.onResume(); validateFields(); } @Override - public void onPause() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onPause"); - } - super.onPause(); - } - - /** - * Called when the Fragment is no longer started. - */ - @Override - public void onStop() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStop"); - } - super.onStop(); - mStarted = false; - } - - /** - * Called when the fragment is no longer in use. - */ - @Override - public void onDestroy() { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onDestroy"); - } - super.onDestroy(); - } - - @Override public void onSaveInstanceState(Bundle outState) { - if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onSaveInstanceState"); - } super.onSaveInstanceState(outState); outState.putBoolean(STATE_KEY_LOADED, mLoaded); } /** - * Activity provides callbacks here. This also triggers loading and setting up the UX - */ - @Override - public void setCallback(Callback callback) { - super.setCallback(callback); - if (mStarted) { - loadSettings(); - } - } - - /** * Load the current settings into the UI */ private void loadSettings() { if (mLoaded) return; - final HostAuth sendAuth = mSetupData.getAccount().getOrCreateHostAuthSend(mContext); + final HostAuth sendAuth = mSetupData.getAccount().getOrCreateHostAuthSend(mAppContext); + if (!mSetupData.isOutgoingCredLoaded()) { + sendAuth.setUserName(mSetupData.getEmail()); + AccountSetupCredentialsFragment.populateHostAuthWithResults(mAppContext, sendAuth, + mSetupData.getCredentialResults()); + final String[] emailParts = mSetupData.getEmail().split("@"); + final String domain = emailParts[1]; + sendAuth.setConnection(sendAuth.mProtocol, domain, HostAuth.PORT_UNKNOWN, + HostAuth.FLAG_NONE); + mSetupData.setOutgoingCredLoaded(true); + } if ((sendAuth.mFlags & HostAuth.FLAG_AUTHENTICATE) != 0) { final String username = sendAuth.mLogin; if (username != null) { @@ -352,45 +294,75 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment mPortView.setText(Integer.toString(port)); } + private static class SaveSettingsLoader extends MailAsyncTaskLoader<Boolean> { + private final SetupDataFragment mSetupData; + private final boolean mSettingsMode; + + private SaveSettingsLoader(Context context, SetupDataFragment setupData, + boolean settingsMode) { + super(context); + mSetupData = setupData; + mSettingsMode = settingsMode; + } + + @Override + public Boolean loadInBackground() { + if (mSettingsMode) { + saveSettingsAfterEdit(getContext(), mSetupData); + } else { + saveSettingsAfterSetup(getContext(), mSetupData); + } + return true; + } + + @Override + protected void onDiscardResult(Boolean result) {} + } + + @Override + public Loader<Boolean> getSaveSettingsLoader() { + return new SaveSettingsLoader(mAppContext, mSetupData, mSettingsMode); + } + /** * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. * Blocking - do not call from UI Thread. */ - @Override - public void saveSettingsAfterEdit() { - final Account account = mSetupData.getAccount(); - final Credential cred = account.mHostAuthRecv.mCredential; + public static void saveSettingsAfterEdit(Context context, SetupDataFragment setupData) { + final Account account = setupData.getAccount(); + final Credential cred = account.mHostAuthSend.mCredential; if (cred != null) { if (cred.isSaved()) { - cred.update(mContext, cred.toContentValues()); + cred.update(context, cred.toContentValues()); } else { - cred.save(mContext); - account.mHostAuthRecv.mCredentialKey = cred.mId; + cred.save(context); + account.mHostAuthSend.mCredentialKey = cred.mId; } } - account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); + account.mHostAuthSend.update(context, account.mHostAuthSend.toContentValues()); // Update the backup (side copy) of the accounts - AccountBackupRestore.backup(mContext); + AccountBackupRestore.backup(context); } /** * Entry point from Activity after entering new settings and verifying them. For setup mode. */ - @Override - public void saveSettingsAfterSetup() { + @SuppressWarnings("unused") + public static void saveSettingsAfterSetup(Context context, SetupDataFragment setupData) { + // No need to do anything here } /** * Entry point from Activity, when "next" button is clicked */ @Override - public void onNext() { + public void collectUserInput() { final Account account = mSetupData.getAccount(); - final HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); + final HostAuth sendAuth = account.getOrCreateHostAuthSend(mAppContext); if (mRequireLoginView.isChecked()) { final String userName = mUsernameView.getText().toString().trim(); - final String userPassword = mAuthenticationView.getPassword().toString(); + final String userPassword = mAuthenticationView.getPassword(); sendAuth.setLogin(userName, userPassword); } else { sendAuth.setLogin(null, null); @@ -402,15 +374,15 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment serverPort = Integer.parseInt(mPortView.getText().toString().trim()); } catch (NumberFormatException e) { serverPort = getPortFromSecurityType(); - LogUtils.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); + LogUtils.d(LogUtils.TAG, "Non-integer server port; using '" + serverPort + "'"); } final int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; sendAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType); sendAuth.mDomain = null; - mCallback.onProceedNext(SetupDataFragment.CHECK_OUTGOING, this); - clearButtonBounce(); + final Callback callback = (Callback) getActivity(); + callback.onAccountServerUIComplete(SetupDataFragment.CHECK_OUTGOING); } @Override @@ -420,10 +392,22 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment @Override public void onRequestSignIn() { - // Launch the signin activity. - // TODO: at some point we should just use the sign in fragment on the main setup activity. - final Intent intent = new Intent(getActivity(), SignInActivity.class); - intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, mSetupData); + // Launch the credential activity. + final String protocol = + mSetupData.getAccount().getOrCreateHostAuthSend(mAppContext).mProtocol; + final Intent intent = AccountCredentials.getAccountCredentialsIntent(getActivity(), + mUsernameView.getText().toString(), protocol); startActivityForResult(intent, SIGN_IN_REQUEST); } + + @Override + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + if (requestCode == SIGN_IN_REQUEST && resultCode == Activity.RESULT_OK) { + final Account account = mSetupData.getAccount(); + final HostAuth sendAuth = account.getOrCreateHostAuthSend(getActivity()); + AccountSetupCredentialsFragment.populateHostAuthWithResults(mAppContext, sendAuth, + data.getExtras()); + mAuthenticationView.setAuthInfo(true, sendAuth); + } + } } diff --git a/src/com/android/email/activity/setup/AccountSetupType.java b/src/com/android/email/activity/setup/AccountSetupType.java deleted file mode 100644 index 83b345f2f..000000000 --- a/src/com/android/email/activity/setup/AccountSetupType.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2008 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 android.app.Activity; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.RelativeLayout; -import android.widget.RelativeLayout.LayoutParams; - -import com.android.email.R; -import com.android.email.activity.UiUtilities; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.HostAuth; -import com.android.emailcommon.utility.Utility; -import com.android.mail.utils.LogUtils; - -/** - * Prompts the user to select an account type. The account type, along with the - * passed in email address, password and makeDefault are then passed on to the - * AccountSetupIncoming activity. - */ -public class AccountSetupType extends AccountSetupActivity implements OnClickListener { - - private boolean mButtonPressed; - - public static void actionSelectAccountType(Activity fromActivity, SetupDataFragment setupData) { - final Intent i = new ForwardingIntent(fromActivity, AccountSetupType.class); - i.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, setupData); - fromActivity.startActivity(i); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final String accountType = mSetupData.getFlowAccountType(); - // If we're in account setup flow mode, see if there's just one protocol that matches - if (mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_ACCOUNT_MANAGER) { - int matches = 0; - String protocol = null; - for (EmailServiceInfo info: EmailServiceUtils.getServiceInfoList(this)) { - if (info.accountType.equals(accountType)) { - protocol = info.protocol; - matches++; - } - } - // If so, select it... - if (matches == 1) { - onSelect(protocol); - return; - } - } - - // Otherwise proceed into this screen - setContentView(R.layout.account_setup_account_type); - final ViewGroup parent = UiUtilities.getView(this, R.id.accountTypes); - View lastView = parent.getChildAt(0); - int i = 1; - for (EmailServiceInfo info: EmailServiceUtils.getServiceInfoList(this)) { - if (EmailServiceUtils.isServiceAvailable(this, info.protocol)) { - // If we're looking for a specific account type, reject others - // Don't show types with "hide" set - if (info.hide || (accountType != null && !accountType.equals(info.accountType))) { - continue; - } - LayoutInflater.from(this).inflate(R.layout.account_type, parent); - final Button button = (Button)parent.getChildAt(i); - if (parent instanceof RelativeLayout) { - final LayoutParams params = (LayoutParams)button.getLayoutParams(); - params.addRule(RelativeLayout.BELOW, lastView.getId()); - } - button.setId(i); - button.setTag(info.protocol); - button.setText(info.name); - button.setOnClickListener(this); - lastView = button; - i++; - // TODO: Remember vendor overlay for exchange name - } - } - final Button previousButton = (Button) findViewById(R.id.previous); // xlarge only - if (previousButton != null) previousButton.setOnClickListener(this); - } - - /** - * The user has selected an exchange account type. Set the mail delete policy here, because - * there is no UI (for exchange), and switch the default sync interval to "push". - */ - private void onSelect(String protocol) { - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - recvAuth.setConnection(protocol, recvAuth.mAddress, recvAuth.mPort, recvAuth.mFlags); - final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, protocol); - - new DuplicateCheckTask(account.mEmailAddress, info.accountType) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void onProceedNext() { - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, recvAuth.mProtocol); - if (info.usesAutodiscover) { - mSetupData.setCheckSettingsMode(SetupDataFragment.CHECK_AUTODISCOVER); - } else { - mSetupData.setCheckSettingsMode( - SetupDataFragment.CHECK_INCOMING | - (info.usesSmtp ? SetupDataFragment.CHECK_OUTGOING : 0)); - } - recvAuth.mLogin = recvAuth.mLogin + "@" + recvAuth.mAddress; - AccountSetupBasics.setDefaultsForProtocol(this, account); - - // XXX this launches the incoming activity. We should only be doing this - // if we are in full on manual mode I think. - AccountSetupIncoming.actionIncomingSettings(this, mSetupData); - - // XXX This launches the signin activity. -// final Intent intent = new Intent(this, SignInActivity.class); -// intent.putExtra(SignInActivity.EXTRA_FLOW_MODE_INITIAL, true); -// intent.putExtra(SignInActivity.EXTRA_MANUAL_SETUP, false); -// intent.putExtra(SetupDataFragment.EXTRA_SETUP_DATA, mSetupData); -// startActivity(intent); - // Back from the incoming screen returns to AccountSetupBasics - finish(); - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.previous: - finish(); - break; - default: - if (!mButtonPressed) { - mButtonPressed = true; - onSelect((String)v.getTag()); - } - break; - } - } - - private class DuplicateCheckTask extends AsyncTask<Void, Void, String> { - private final String mAddress; - private final String mAuthority; - - public DuplicateCheckTask(String address, String authority) { - mAddress = address; - mAuthority = authority; - } - - @Override - protected String doInBackground(Void... params) { - return Utility.findExistingAccount(AccountSetupType.this, mAuthority, mAddress); - } - - @Override - protected void onPostExecute(String duplicateAccountName) { - mButtonPressed = false; - if (duplicateAccountName != null) { - // Show duplicate account warning - final DuplicateAccountDialogFragment dialogFragment = - DuplicateAccountDialogFragment.newInstance(duplicateAccountName); - dialogFragment.show(AccountSetupType.this.getFragmentManager(), - DuplicateAccountDialogFragment.TAG); - } else { - // Otherwise, proceed with the save/check - onProceedNext(); - } - } - - @Override - protected void onCancelled(String s) { - mButtonPressed = false; - LogUtils.d(LogUtils.TAG, "Duplicate account check cancelled (AccountSetupType)"); - } - } -} diff --git a/src/com/android/email/activity/setup/AccountSetupTypeFragment.java b/src/com/android/email/activity/setup/AccountSetupTypeFragment.java new file mode 100644 index 000000000..1bfd536cc --- /dev/null +++ b/src/com/android/email/activity/setup/AccountSetupTypeFragment.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 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 android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.RelativeLayout; + +import com.android.email.R; +import com.android.email.activity.UiUtilities; +import com.android.email.service.EmailServiceUtils; + +public class AccountSetupTypeFragment extends AccountSetupFragment + implements View.OnClickListener { + + public interface Callback extends AccountSetupFragment.Callback { + /** + * called when the user has selected a protocol type for the account + * @param protocol {@link EmailServiceUtils.EmailServiceInfo#protocol} + */ + void onChooseProtocol(String protocol); + } + + public static AccountSetupTypeFragment newInstance() { + return new AccountSetupTypeFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.account_setup_type_fragment, container, false); + + final Context appContext = inflater.getContext().getApplicationContext(); + + final ViewGroup parent = UiUtilities.getView(view, R.id.accountTypes); + View lastView = parent.getChildAt(0); + int i = 1; + for (final EmailServiceUtils.EmailServiceInfo info + : EmailServiceUtils.getServiceInfoList(appContext)) { + if (EmailServiceUtils.isServiceAvailable(appContext, info.protocol)) { + // Don't show types with "hide" set + if (info.hide) { + continue; + } + inflater.inflate(R.layout.account_type, parent); + final Button button = (Button)parent.getChildAt(i); + if (parent instanceof RelativeLayout) { + final RelativeLayout.LayoutParams params = + (RelativeLayout.LayoutParams)button.getLayoutParams(); + params.addRule(RelativeLayout.BELOW, lastView.getId()); + } + button.setId(i); + button.setTag(info.protocol); + button.setText(info.name); + button.setOnClickListener(this); + lastView = button; + i++; + } + } + + return view; + } + + @Override + public void onClick(View v) { + final String protocol = (String) v.getTag(); + final Callback callback = (Callback) getActivity(); + callback.onChooseProtocol(protocol); + } +} diff --git a/src/com/android/email/activity/setup/CheckSettingsErrorDialogFragment.java b/src/com/android/email/activity/setup/CheckSettingsErrorDialogFragment.java new file mode 100644 index 000000000..558eedc6d --- /dev/null +++ b/src/com/android/email/activity/setup/CheckSettingsErrorDialogFragment.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2014 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 android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.TextUtils; + +import com.android.email.R; +import com.android.emailcommon.mail.MessagingException; +import com.android.mail.utils.LogUtils; + +public class CheckSettingsErrorDialogFragment extends DialogFragment{ + public final static String TAG = "CheckSettingsErrorDialog"; + + public final static int REASON_OTHER = 0; + public final static int REASON_AUTHENTICATION_FAILED = 1; + public final static int REASON_CERTIFICATE_REQUIRED = 2; + + // Bundle keys for arguments + private final static String ARGS_MESSAGE = "CheckSettingsErrorDialog.Message"; + private final static String ARGS_REASON = "CheckSettingsErrorDialog.ExceptionId"; + + public interface Callback { + /** + * Called to indicate the user wants to resolve the error by changing the client certificate + */ + void onCheckSettingsErrorDialogEditCertificate(); + + /** + * Called to indicate the user wants to resolve the error by editing the server settings + */ + void onCheckSettingsErrorDialogEditSettings(); + } + + public CheckSettingsErrorDialogFragment() {} + + /** + * @param reason see REASON_* constants + * @param message from {@link #getErrorString(Context, MessagingException)} + * @return new instance + */ + public static CheckSettingsErrorDialogFragment newInstance(int reason, String message) { + final CheckSettingsErrorDialogFragment fragment = new CheckSettingsErrorDialogFragment(); + final Bundle arguments = new Bundle(2); + arguments.putString(ARGS_MESSAGE, message); + arguments.putInt(ARGS_REASON, reason); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final Bundle arguments = getArguments(); + final String message = arguments.getString(ARGS_MESSAGE); + final int reason = arguments.getInt(ARGS_REASON); + final Callback callback = (Callback) getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setMessage(message) + .setCancelable(true); + + // Use a different title when we get + // MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED + if (reason == REASON_AUTHENTICATION_FAILED) { + builder.setTitle(R.string.account_setup_autodiscover_dlg_authfail_title); + } else { + builder.setIconAttribute(android.R.attr.alertDialogIcon) + .setTitle(context.getString(R.string.account_setup_failed_dlg_title)); + } + + if (reason == REASON_CERTIFICATE_REQUIRED) { + // Certificate error - show two buttons so the host fragment can auto pop + // into the appropriate flow. + builder.setPositiveButton( + context.getString(android.R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + callback.onCheckSettingsErrorDialogEditCertificate(); + } + }); + builder.setNegativeButton( + context.getString(android.R.string.cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + callback.onCheckSettingsErrorDialogEditSettings(); + } + }); + + } else { + // "Normal" error - just use a single "Edit details" button. + builder.setPositiveButton( + context.getString(R.string.account_setup_failed_dlg_edit_details_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + callback.onCheckSettingsErrorDialogEditSettings(); + } + }); + } + return builder.create(); + } + + public static int getReasonFromException (MessagingException ex) { + final int exceptionCode = ex.getExceptionType(); + switch (exceptionCode) { + case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: + return REASON_AUTHENTICATION_FAILED; + case MessagingException.CLIENT_CERTIFICATE_REQUIRED: + return REASON_CERTIFICATE_REQUIRED; + } + return REASON_OTHER; + } + + public static String getErrorString(Context context, MessagingException ex) { + final int id; + String message = ex.getMessage(); + if (message != null) { + message = message.trim(); + } + switch (ex.getExceptionType()) { + // 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 = TextUtils.isEmpty(message) + ? R.string.account_setup_failed_dlg_certificate_message + : R.string.account_setup_failed_dlg_certificate_message_fmt; + break; + case MessagingException.AUTHENTICATION_FAILED: + id = R.string.account_setup_failed_dlg_auth_message; + break; + case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: + id = R.string.account_setup_autodiscover_dlg_authfail_message; + break; + case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR: + id = R.string.account_setup_failed_check_credentials_message; + break; + case MessagingException.IOERROR: + id = R.string.account_setup_failed_ioerror; + break; + case MessagingException.TLS_REQUIRED: + id = R.string.account_setup_failed_tls_required; + break; + case MessagingException.AUTH_REQUIRED: + id = R.string.account_setup_failed_auth_required; + break; + case MessagingException.SECURITY_POLICIES_UNSUPPORTED: + id = R.string.account_setup_failed_security_policies_unsupported; + // Belt and suspenders here; there should always be a non-empty array here + String[] unsupportedPolicies = (String[]) ex.getExceptionData(); + if (unsupportedPolicies == null) { + LogUtils.w(LogUtils.TAG, "No data for unsupported policies?"); + break; + } + // Build a string, concatenating policies we don't support + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String policyName: unsupportedPolicies) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(policyName); + } + message = sb.toString(); + break; + case MessagingException.ACCESS_DENIED: + id = R.string.account_setup_failed_access_denied; + break; + case MessagingException.PROTOCOL_VERSION_UNSUPPORTED: + id = R.string.account_setup_failed_protocol_unsupported; + break; + case MessagingException.GENERAL_SECURITY: + id = R.string.account_setup_failed_security; + break; + case MessagingException.CLIENT_CERTIFICATE_REQUIRED: + id = R.string.account_setup_failed_certificate_required; + break; + case MessagingException.CLIENT_CERTIFICATE_ERROR: + id = R.string.account_setup_failed_certificate_inaccessible; + break; + default: + id = TextUtils.isEmpty(message) + ? R.string.account_setup_failed_dlg_server_message + : R.string.account_setup_failed_dlg_server_message_fmt; + break; + } + return TextUtils.isEmpty(message) + ? context.getString(id) + : context.getString(id, message); + } +} diff --git a/src/com/android/email/activity/setup/CheckSettingsProgressDialogFragment.java b/src/com/android/email/activity/setup/CheckSettingsProgressDialogFragment.java new file mode 100644 index 000000000..661f9792f --- /dev/null +++ b/src/com/android/email/activity/setup/CheckSettingsProgressDialogFragment.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2014 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 android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.email.R; + +/** + * 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. + */ +public class CheckSettingsProgressDialogFragment extends DialogFragment { + public final static String TAG = "CheckProgressDialog"; + + // Extras for saved instance state + private final static String ARGS_PROGRESS_STRING = "CheckProgressDialog.Progress"; + private final static String ARGS_MODE_INT = "CheckProgressDialog.Mode"; + + // UI + private String mProgressString; + + public interface Callback { + void onCheckSettingsProgressDialogCancel(); + } + + // Public no-args constructor needed for fragment re-instantiation + public CheckSettingsProgressDialogFragment() {} + + /** + * Create a dialog that reports progress + * @param checkMode check settings mode + */ + public static CheckSettingsProgressDialogFragment newInstance(int checkMode) { + final CheckSettingsProgressDialogFragment f = new CheckSettingsProgressDialogFragment(); + final Bundle b = new Bundle(1); + b.putInt(ARGS_MODE_INT, checkMode); + f.setArguments(b); + return f; + } + + /** + * Update the progress of an existing dialog + * @param progress latest progress to be displayed + */ + protected void updateProgress(int progress) { + mProgressString = AccountCheckSettingsFragment.getProgressString(getActivity(), progress); + final AlertDialog dialog = (AlertDialog) getDialog(); + if (dialog != null && mProgressString != null) { + dialog.setMessage(mProgressString); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + if (savedInstanceState != null) { + mProgressString = savedInstanceState.getString(ARGS_PROGRESS_STRING); + } + if (mProgressString == null) { + final int checkMode = getArguments().getInt(ARGS_MODE_INT); + final int progress = AccountCheckSettingsFragment.getProgressForMode(checkMode); + mProgressString = AccountCheckSettingsFragment.getProgressString(getActivity(), + progress); + } + + final ProgressDialog dialog = new ProgressDialog(context); + dialog.setIndeterminate(true); + dialog.setMessage(mProgressString); + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, + context.getString(R.string.cancel_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + + final Callback callback = (Callback) getActivity(); + callback.onCheckSettingsProgressDialogCancel(); + } + }); + return dialog; + } + + /** + * Listen for cancellation, which can happen from places other than the + * negative button (e.g. touching outside the dialog), and stop the checker + */ + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + final Callback callback = (Callback) getActivity(); + callback.onCheckSettingsProgressDialogCancel(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(ARGS_PROGRESS_STRING, mProgressString); + } +} diff --git a/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java b/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java index 4211e6d07..5e135c665 100644 --- a/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java +++ b/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java @@ -33,8 +33,6 @@ import java.io.IOException; */ public class OAuthAuthenticationActivity extends Activity implements LoaderCallbacks<AuthenticationResult> { - private final static String TAG = Logging.LOG_TAG; - public static final String EXTRA_EMAIL_ADDRESS = "email_address"; public static final String EXTRA_PROVIDER = "provider"; public static final String EXTRA_PROVIDER_ID = "provider_id"; diff --git a/src/com/android/email/activity/setup/SecurityRequiredDialogFragment.java b/src/com/android/email/activity/setup/SecurityRequiredDialogFragment.java new file mode 100644 index 000000000..f0baf7c61 --- /dev/null +++ b/src/com/android/email/activity/setup/SecurityRequiredDialogFragment.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 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 android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.email.R; + +/** + * 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 class SecurityRequiredDialogFragment extends DialogFragment { + public final static String TAG = "SecurityRequiredDialog"; + + // Bundle keys for arguments + private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName"; + + public interface Callback { + + /** + * Callback for the result of this dialog fragment + * @param ok True for OK pressed, false for cancel + */ + void onSecurityRequiredDialogResult(boolean ok); + } + + // Public no-args constructor needed for fragment re-instantiation + public SecurityRequiredDialogFragment() {} + + public static SecurityRequiredDialogFragment newInstance(String hostName) { + final SecurityRequiredDialogFragment fragment = new SecurityRequiredDialogFragment(); + final Bundle arguments = new Bundle(1); + arguments.putString(ARGS_HOST_NAME, hostName); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final Bundle arguments = getArguments(); + final String hostName = arguments.getString(ARGS_HOST_NAME); + final Callback callback = (Callback) getActivity(); + + return new AlertDialog.Builder(context) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setTitle(context.getString(R.string.account_setup_security_required_title)) + .setMessage(context.getString( + R.string.account_setup_security_policies_required_fmt, hostName)) + .setCancelable(true) + .setPositiveButton( + context.getString(R.string.okay_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + callback.onSecurityRequiredDialogResult(true); + } + }) + .setNegativeButton( + context.getString(R.string.cancel_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + callback.onSecurityRequiredDialogResult(false); + } + }) + .create(); + } +} diff --git a/src/com/android/email/activity/setup/SetupDataFragment.java b/src/com/android/email/activity/setup/SetupDataFragment.java index 8a468eef0..48f1be9b0 100644 --- a/src/com/android/email/activity/setup/SetupDataFragment.java +++ b/src/com/android/email/activity/setup/SetupDataFragment.java @@ -1,6 +1,5 @@ package com.android.email.activity.setup; -import android.accounts.AccountAuthenticatorResponse; import android.app.Fragment; import android.os.Bundle; import android.os.Parcel; @@ -25,6 +24,7 @@ public class SetupDataFragment extends Fragment implements Parcelable { public static final int FLOW_MODE_FORCE_CREATE = 4; // The following two modes are used to "pop the stack" and return from the setup flow. We // either return to the caller (if we're in an account type flow) or go to the message list + // TODO: figure out if we still care about these public static final int FLOW_MODE_RETURN_TO_CALLER = 5; public static final int FLOW_MODE_RETURN_TO_MESSAGE_LIST = 6; public static final int FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT = 7; @@ -35,45 +35,35 @@ public class SetupDataFragment extends Fragment implements Parcelable { public static final int CHECK_OUTGOING = 2; public static final int CHECK_AUTODISCOVER = 4; - private static final String SAVESTATE_FLOWMODE = "flowMode"; - private static final String SAVESTATE_FLOWACCOUNTTYPE = "flowAccountType"; - private static final String SAVESTATE_ACCOUNT = "account"; - private static final String SAVESTATE_USERNAME = "username"; - private static final String SAVESTATE_PASSWORD = "password"; - private static final String SAVESTATE_CHECKSETTINGSMODE = "checkSettingsMode"; - private static final String SAVESTATE_ALLOWAUTODISCOVER = "allowAutoDiscover"; - private static final String SAVESTATE_POLICY = "policy"; - private static final String SAVESTATE_ACCOUNTAUTHENTICATORRESPONSE = - "accountAuthenticatorResponse"; - private static final String SAVESTATE_REPORT_AUTHENTICATION_ERROR = - "reportAuthenticationError"; + private static final String SAVESTATE_FLOWMODE = "SetupDataFragment.flowMode"; + private static final String SAVESTATE_ACCOUNT = "SetupDataFragment.account"; + private static final String SAVESTATE_EMAIL = "SetupDataFragment.email"; + private static final String SAVESTATE_CREDENTIAL = "SetupDataFragment.credential"; + private static final String SAVESTATE_INCOMING_LOADED = "SetupDataFragment.incomingLoaded"; + private static final String SAVESTATE_OUTGOING_LOADED = "SetupDataFragment.outgoingLoaded"; + private static final String SAVESTATE_POLICY = "SetupDataFragment.policy"; // All access will be through getters/setters private int mFlowMode = FLOW_MODE_NORMAL; - private String mFlowAccountType; private Account mAccount; - private String mUsername; - private String mPassword; - private int mCheckSettingsMode = 0; - private boolean mAllowAutodiscover = true; - private Policy mPolicy; - private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; - private boolean mReportAccountAuthenticationError = false; + private String mEmail; + private Bundle mCredentialResults; + // These are used to track whether we've preloaded the login credentials into incoming/outgoing + // settings. Set them to 'true' by default, and false when we change the credentials or email + private boolean mIncomingCredLoaded = true; + private boolean mOutgoingCredLoaded = true; + // This is accessed off-thread in AccountCheckSettingsFragment + private volatile Policy mPolicy; public interface SetupDataContainer { public SetupDataFragment getSetupData(); - public void setSetupData(SetupDataFragment setupData); } public SetupDataFragment() { mPolicy = null; - mAllowAutodiscover = true; - mCheckSettingsMode = 0; mAccount = new Account(); - mUsername = null; - mPassword = null; - mAccountAuthenticatorResponse = null; - mReportAccountAuthenticationError = false; + mEmail = null; + mCredentialResults = null; } public SetupDataFragment(int flowMode) { @@ -81,11 +71,6 @@ public class SetupDataFragment extends Fragment implements Parcelable { mFlowMode = flowMode; } - public SetupDataFragment(int flowMode, String accountType) { - this(flowMode); - mFlowAccountType = accountType; - } - public SetupDataFragment(int flowMode, Account account) { this(flowMode); mAccount = account; @@ -95,17 +80,12 @@ public class SetupDataFragment extends Fragment implements Parcelable { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(SAVESTATE_FLOWMODE, mFlowMode); - outState.putString(SAVESTATE_FLOWACCOUNTTYPE, mFlowAccountType); outState.putParcelable(SAVESTATE_ACCOUNT, mAccount); - outState.putString(SAVESTATE_USERNAME, mUsername); - outState.putString(SAVESTATE_PASSWORD, mPassword); - outState.putInt(SAVESTATE_CHECKSETTINGSMODE, mCheckSettingsMode); - outState.putBoolean(SAVESTATE_ALLOWAUTODISCOVER, mAllowAutodiscover); + outState.putString(SAVESTATE_EMAIL, mEmail); + outState.putParcelable(SAVESTATE_CREDENTIAL, mCredentialResults); + outState.putBoolean(SAVESTATE_INCOMING_LOADED, mIncomingCredLoaded); + outState.putBoolean(SAVESTATE_OUTGOING_LOADED, mOutgoingCredLoaded); outState.putParcelable(SAVESTATE_POLICY, mPolicy); - outState.putParcelable(SAVESTATE_ACCOUNTAUTHENTICATORRESPONSE, - mAccountAuthenticatorResponse); - outState.putBoolean(SAVESTATE_REPORT_AUTHENTICATION_ERROR, - mReportAccountAuthenticationError); } @Override @@ -113,17 +93,12 @@ public class SetupDataFragment extends Fragment implements Parcelable { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mFlowMode = savedInstanceState.getInt(SAVESTATE_FLOWMODE); - mFlowAccountType = savedInstanceState.getString(SAVESTATE_FLOWACCOUNTTYPE); mAccount = savedInstanceState.getParcelable(SAVESTATE_ACCOUNT); - mUsername = savedInstanceState.getString(SAVESTATE_USERNAME); - mPassword = savedInstanceState.getString(SAVESTATE_PASSWORD); - mCheckSettingsMode = savedInstanceState.getInt(SAVESTATE_CHECKSETTINGSMODE); - mAllowAutodiscover = savedInstanceState.getBoolean(SAVESTATE_ALLOWAUTODISCOVER); + mEmail = savedInstanceState.getString(SAVESTATE_EMAIL); + mCredentialResults = savedInstanceState.getParcelable(SAVESTATE_CREDENTIAL); + mIncomingCredLoaded = savedInstanceState.getBoolean(SAVESTATE_INCOMING_LOADED); + mOutgoingCredLoaded = savedInstanceState.getBoolean(SAVESTATE_OUTGOING_LOADED); mPolicy = savedInstanceState.getParcelable(SAVESTATE_POLICY); - mAccountAuthenticatorResponse = - savedInstanceState.getParcelable(SAVESTATE_ACCOUNTAUTHENTICATORRESPONSE); - mReportAccountAuthenticationError = - savedInstanceState.getBoolean(SAVESTATE_REPORT_AUTHENTICATION_ERROR, false); } setRetainInstance(true); } @@ -137,14 +112,6 @@ public class SetupDataFragment extends Fragment implements Parcelable { mFlowMode = flowMode; } - public String getFlowAccountType() { - return mFlowAccountType; - } - - public void setFlowAccountType(String flowAccountType) { - mFlowAccountType = flowAccountType; - } - public Account getAccount() { return mAccount; } @@ -153,63 +120,51 @@ public class SetupDataFragment extends Fragment implements Parcelable { mAccount = account; } - public String getUsername() { - return mUsername; + public String getEmail() { + return mEmail; } - public void setUsername(String username) { - mUsername = username; + public void setEmail(String email) { + mEmail = email; + mAccount.mEmailAddress = email; + mIncomingCredLoaded = false; + mOutgoingCredLoaded = false; } - public String getPassword() { - return mPassword; + public Bundle getCredentialResults() { + return mCredentialResults; } - public void setPassword(String password) { - mPassword = password; + public void setCredentialResults(Bundle credentialResults) { + mCredentialResults = credentialResults; + mIncomingCredLoaded = false; + mOutgoingCredLoaded = false; } - public int getCheckSettingsMode() { - return mCheckSettingsMode; + public boolean isIncomingCredLoaded() { + return mIncomingCredLoaded; } - public void setCheckSettingsMode(int checkSettingsMode) { - mCheckSettingsMode = checkSettingsMode; + public void setIncomingCredLoaded(boolean incomingCredLoaded) { + mIncomingCredLoaded = incomingCredLoaded; } - public boolean isAllowAutodiscover() { - return mAllowAutodiscover; + public boolean isOutgoingCredLoaded() { + return mOutgoingCredLoaded; } - public void setAllowAutodiscover(boolean allowAutodiscover) { - mAllowAutodiscover = allowAutodiscover; + public void setOutgoingCredLoaded(boolean outgoingCredLoaded) { + mOutgoingCredLoaded = outgoingCredLoaded; } - public Policy getPolicy() { + public synchronized Policy getPolicy() { return mPolicy; } - public void setPolicy(Policy policy) { + public synchronized void setPolicy(Policy policy) { mPolicy = policy; } - public AccountAuthenticatorResponse getAccountAuthenticatorResponse() { - return mAccountAuthenticatorResponse; - } - - public void setAccountAuthenticatorResponse( - AccountAuthenticatorResponse accountAuthenticatorResponse) { - mAccountAuthenticatorResponse = accountAuthenticatorResponse; - } - - public boolean getReportAccountAuthenticationError() { - return mReportAccountAuthenticationError; - } - - public void setReportAccountAuthenticationError(final boolean report) { - mReportAccountAuthenticationError = report; - } - // Parcelable methods @Override public int describeContents() { @@ -232,47 +187,38 @@ public class SetupDataFragment extends Fragment implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mFlowMode); - dest.writeString(mFlowAccountType); dest.writeParcelable(mAccount, 0); - dest.writeString(mUsername); - dest.writeString(mPassword); - dest.writeInt(mCheckSettingsMode); - dest.writeInt(mAllowAutodiscover ? 1 : 0); + dest.writeString(mEmail); + dest.writeParcelable(mCredentialResults, 0); + dest.writeBooleanArray(new boolean[] {mIncomingCredLoaded, mOutgoingCredLoaded}); dest.writeParcelable(mPolicy, 0); - dest.writeParcelable(mAccountAuthenticatorResponse, 0); } public SetupDataFragment(Parcel in) { final ClassLoader loader = getClass().getClassLoader(); mFlowMode = in.readInt(); - mFlowAccountType = in.readString(); mAccount = in.readParcelable(loader); - mUsername = in.readString(); - mPassword = in.readString(); - mCheckSettingsMode = in.readInt(); - mAllowAutodiscover = in.readInt() == 1; + mEmail = in.readString(); + mCredentialResults = in.readParcelable(loader); + final boolean[] credsLoaded = in.createBooleanArray(); + mIncomingCredLoaded = credsLoaded[0]; + mOutgoingCredLoaded = credsLoaded[1]; mPolicy = in.readParcelable(loader); - mAccountAuthenticatorResponse = in.readParcelable(loader); } - public String debugString() { + @Override + public String toString() { final StringBuilder sb = new StringBuilder("SetupData"); sb.append(":acct="); sb.append(mAccount == null ? "none" :mAccount.mId); - if (mUsername != null) { + if (mEmail != null) { sb.append(":user="); - sb.append(mUsername); + sb.append(mEmail); } - if (mPassword != null) { - sb.append(":pass="); - sb.append(mPassword); + if (mCredentialResults != null) { + sb.append(":cred="); + sb.append(mCredentialResults.toString()); } - sb.append(":a/d="); - sb.append(mAllowAutodiscover); - sb.append(":check="); - if ((mCheckSettingsMode & CHECK_INCOMING) != 0) sb.append("in+"); - if ((mCheckSettingsMode & CHECK_OUTGOING) != 0) sb.append("out+"); - if ((mCheckSettingsMode & CHECK_AUTODISCOVER) != 0) sb.append("a/d"); sb.append(":policy="); sb.append(mPolicy == null ? "none" : "exists"); return sb.toString(); diff --git a/src/com/android/email/activity/setup/SignInActivity.java b/src/com/android/email/activity/setup/SignInActivity.java deleted file mode 100644 index bab5c5a53..000000000 --- a/src/com/android/email/activity/setup/SignInActivity.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.android.email.activity.setup; - -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.ImageButton; - -import com.android.email.R; -import com.android.email.activity.UiUtilities; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.emailcommon.Logging; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.Credential; -import com.android.emailcommon.provider.HostAuth; -import com.android.mail.utils.LogUtils; - -public class SignInActivity extends AccountSetupActivity implements SignInFragment.SignInCallback, - View.OnClickListener, AccountCheckSettingsFragment.Callbacks{ - - public static final String EXTRA_MANUAL_SETUP = "manual"; - public static final String EXTRA_FLOW_MODE_INITIAL = "initial"; - - public static final String EXTRA_PASSWORD = "password"; - public static final String EXTRA_OAUTH_PROVIDER = "provider"; - public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken"; - public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken"; - public static final String EXTRA_OAUTH_EXPIRES_IN_SECONDS = "expiresInSeconds"; - - private SignInFragment mFragment; - private ImageButton mNextButton; - private ImageButton mPrevButton; - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.sign_in_activity); - final String emailAddress = mSetupData.getAccount().mEmailAddress; - mFragment = (SignInFragment) - getFragmentManager().findFragmentById(R.id.sign_in_fragment); - mFragment.setEmailAddress(emailAddress); - - mFragment.setSignInCallback(this); - mNextButton = UiUtilities.getView(this, R.id.next); - mPrevButton = UiUtilities.getView(this, R.id.previous); - mNextButton.setOnClickListener(this); - mPrevButton.setOnClickListener(this); - - // Assume canceled until we find out otherwise. - setResult(RESULT_CANCELED); - } - - @Override - public void onOAuthSignIn(final String providerId, final String accessToken, - final String refreshToken, final int expiresInSeconds) { - if (getIntent().getBooleanExtra(EXTRA_FLOW_MODE_INITIAL, false)) { - // On initial setup, we now try to validate the account. - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - final Credential cred = recvAuth.getOrCreateCredential(this); - cred.mProviderId = providerId; - cred.mAccessToken = accessToken; - cred.mRefreshToken = refreshToken; - cred.mExpiration = System.currentTimeMillis() + - expiresInSeconds * DateUtils.SECOND_IN_MILLIS; - - // TODO: For now, assume that we will use SSL because that's what - // gmail wants. This needs to be parameterized from providers.xml - recvAuth.mFlags |= HostAuth.FLAG_SSL; - recvAuth.mFlags |= HostAuth.FLAG_OAUTH; - - final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - sendAuth.mCredential = cred; - sendAuth.mFlags |= HostAuth.FLAG_SSL; - sendAuth.mFlags |= HostAuth.FLAG_OAUTH; - startAuthenticationCheck(); - } else { - // On regular settings, we just return the auth info to the caller. - final Intent intent = new Intent(); - intent.putExtra(EXTRA_OAUTH_PROVIDER, providerId); - intent.putExtra(EXTRA_OAUTH_ACCESS_TOKEN, accessToken); - intent.putExtra(EXTRA_OAUTH_REFRESH_TOKEN, refreshToken); - intent.putExtra(EXTRA_OAUTH_EXPIRES_IN_SECONDS, expiresInSeconds); - setResult(RESULT_OK, intent); - finish(); - } - } - - private void onNext() { - final String password = mFragment.getPassword(); - // This only applies for password authentication. - if (getIntent().getBooleanExtra(EXTRA_FLOW_MODE_INITIAL, false)) { - // On initial setup, we now try to validate the account. - - final Account account = mSetupData.getAccount(); - final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - recvAuth.mPassword = password; - - final HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - sendAuth.mPassword = password; - - startAuthenticationCheck(); - } else { - // On regular settings, we just return the auth info to the caller. - final Intent intent = new Intent(); - intent.putExtra(EXTRA_PASSWORD, password); - setResult(RESULT_OK, intent); - finish(); - } - } - - private void startAuthenticationCheck() { - final AccountCheckSettingsFragment checkerFragment = - AccountCheckSettingsFragment.newInstance( - SetupDataFragment.CHECK_INCOMING | SetupDataFragment.CHECK_OUTGOING, - null); - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG); - transaction.addToBackStack(null); - transaction.commit(); - } - - @Override - public void onValidate() { - mNextButton.setEnabled(!TextUtils.isEmpty(mFragment.getPassword())); - } - - @Override - public void onClick(View view) { - if (view == mNextButton) { - onNext(); - } else if (view == mPrevButton) { - onBackPressed(); - } - } - - /** - * Implements AccountCheckSettingsFragment.Callbacks - * - * This is used in automatic setup mode to jump directly down to the options screen. - * - * This is the only case where we finish() this activity but account setup is continuing, - * so we inhibit reporting any error back to the Account manager. - */ - @Override - public void onCheckSettingsComplete(int result, SetupDataFragment setupData) { - mSetupData = setupData; - if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { - AccountSetupFinal.actionFinal(this, mSetupData); - mSetupData.setReportAccountAuthenticationError(false); - finish(); - } else { - // FLAG: DO I need to do anything else here? - LogUtils.d(Logging.LOG_TAG, "failure on check setup"); - } - } - - /** - * Implements AccountCheckSettingsFragment.Callbacks - * This is overridden only by AccountSetupIncoming - */ - @Override - public void onAutoDiscoverComplete(int result, SetupDataFragment setupData) { - throw new IllegalStateException(); - } -} diff --git a/src/com/android/email/activity/setup/SignInFragment.java b/src/com/android/email/activity/setup/SignInFragment.java deleted file mode 100644 index f0ee15fdb..000000000 --- a/src/com/android/email/activity/setup/SignInFragment.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2014 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 android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.Toast; - -import com.android.email.R; -import com.android.email.activity.UiUtilities; -import com.android.email.activity.setup.SetupDataFragment.SetupDataContainer; -import com.android.email.service.EmailServiceUtils; -import com.android.email.service.EmailServiceUtils.EmailServiceInfo; -import com.android.emailcommon.Logging; -import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; -import com.android.emailcommon.VendorPolicyLoader.Provider; -import com.android.emailcommon.provider.HostAuth; -import com.android.mail.utils.LogUtils; - -import java.util.List; - -public class SignInFragment extends Fragment implements OnClickListener { - - private View mOAuthGroup; - private View mOAuthButton; - private EditText mImapPasswordText; - private EditText mRegularPasswordText; - private TextWatcher mValidationTextWatcher; - private String mEmailAddress; - private EmailServiceInfo mServiceInfo; - private String mProviderId; - private SignInCallback mCallback; - private Context mContext; - - public interface SignInCallback { - public void onOAuthSignIn(final String providerId, final String accessToken, - final String refreshToken, final int expiresInSeconds); - - public void onValidate(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.sign_in_fragment, container, false); - - mImapPasswordText = UiUtilities.getView(view, R.id.imap_password); - mRegularPasswordText = UiUtilities.getView(view, R.id.regular_password); - mOAuthGroup = UiUtilities.getView(view, R.id.oauth_group); - mOAuthButton = UiUtilities.getView(view, R.id.sign_in_with_google); - mOAuthButton.setOnClickListener(this); - - // After any text edits, call validateFields() which enables or disables the Next button - mValidationTextWatcher = new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - validatePassword(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } - }; - mImapPasswordText.addTextChangedListener(mValidationTextWatcher); - mRegularPasswordText.addTextChangedListener(mValidationTextWatcher); - - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - LogUtils.d(Logging.LOG_TAG, "onActivityCreated"); - mContext = getActivity(); - if (mContext instanceof SetupDataContainer) { - final SetupDataContainer setupContainer = (SetupDataContainer)mContext; - SetupDataFragment setupData = setupContainer.getSetupData(); - final HostAuth hostAuth = setupData.getAccount().getOrCreateHostAuthRecv(mContext); - mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, - hostAuth.mProtocol); - - if (mServiceInfo.offerOAuth) { - mOAuthGroup.setVisibility(View.VISIBLE); - mRegularPasswordText.setVisibility(View.GONE); - } else { - mOAuthGroup.setVisibility(View.GONE); - mRegularPasswordText.setVisibility(View.VISIBLE); - } - } - validatePassword(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mImapPasswordText.removeTextChangedListener(mValidationTextWatcher); - mImapPasswordText = null; - mRegularPasswordText.removeTextChangedListener(mValidationTextWatcher); - mRegularPasswordText = null; - } - - public void validatePassword() { - mCallback.onValidate(); - // Warn (but don't prevent) if password has leading/trailing spaces - AccountSettingsUtils.checkPasswordSpaces(mContext, mImapPasswordText); - AccountSettingsUtils.checkPasswordSpaces(mContext, mRegularPasswordText); - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, - final Intent data) { - if (requestCode == OAuthAuthenticationActivity.REQUEST_OAUTH) { - if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_SUCCESS) { - final String accessToken = data.getStringExtra( - OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN); - final String refreshToken = data.getStringExtra( - OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN); - final int expiresInSeconds = data.getIntExtra( - OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0); - mCallback.onOAuthSignIn(mProviderId, accessToken, refreshToken, expiresInSeconds); - } else if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_FAILURE - || resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_USER_CANCELED) { - LogUtils.i(Logging.LOG_TAG, "Result from oauth %d", resultCode); - } else { - LogUtils.wtf(Logging.LOG_TAG, "Unknown result code from OAUTH: %d", resultCode); - } - } else { - LogUtils.e(Logging.LOG_TAG, "Unknown request code for onActivityResult in" - + " AccountSetupBasics: %d", requestCode); - } - } - - @Override - public void onClick(View view) { - if (view == mOAuthButton) { - List<OAuthProvider> oauthProviders = AccountSettingsUtils.getAllOAuthProviders( - mContext); - // FLAG currently the only oauth provider we support is google. - // If we ever have more than 1 oauth provider, then we need to implement some sort - // of picker UI. For now, just always take the first oauth provider. - if (oauthProviders.size() > 0) { - mProviderId = oauthProviders.get(0).id; - final Intent i = new Intent(mContext, OAuthAuthenticationActivity.class); - i.putExtra(OAuthAuthenticationActivity.EXTRA_EMAIL_ADDRESS, mEmailAddress); - i.putExtra(OAuthAuthenticationActivity.EXTRA_PROVIDER, mProviderId); - startActivityForResult(i, OAuthAuthenticationActivity.REQUEST_OAUTH); - } - } - } - - public void setEmailAddress(final String emailAddress) { - mEmailAddress = emailAddress; - } - - public String getEmailAddress() { - return mEmailAddress; - } - - public EmailServiceInfo setServiceInfo() { - return mServiceInfo; - } - public String getPassword() { - if (mServiceInfo.offerOAuth) { - return mImapPasswordText.getText().toString(); - } else { - return mRegularPasswordText.getText().toString(); - } - } - - public void setSignInCallback(SignInCallback callback) { - mCallback = callback; - } -} diff --git a/src/com/android/email/mail/Sender.java b/src/com/android/email/mail/Sender.java index 49f861e89..4e85d70fa 100644 --- a/src/com/android/email/mail/Sender.java +++ b/src/com/android/email/mail/Sender.java @@ -117,15 +117,6 @@ public abstract class Sender { return sender; } - /** - * Get class of SettingActivity for this Sender class. - * @return Activity class that has class method actionEditOutgoingSettings(). - */ - public Class<? extends android.app.Activity> getSettingActivityClass() { - // default SettingActivity class - return com.android.email.activity.setup.AccountSetupOutgoing.class; - } - public abstract void open() throws MessagingException; public abstract void sendMessage(long messageId) throws MessagingException; diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index 932e462d6..b14e58d34 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -114,13 +114,8 @@ public class ImapStore extends Store { mTransport = new MailTransport(context, "IMAP", recvAuth); String[] userInfo = recvAuth.getLogin(); - if (userInfo != null) { - mUsername = userInfo[0]; - mPassword = userInfo[1]; - } else { - mUsername = null; - mPassword = null; - } + mUsername = userInfo[0]; + mPassword = userInfo[1]; final Credential cred = recvAuth.getCredential(context); mUseOAuth = (cred != null); mPathPrefix = recvAuth.mDomain; diff --git a/src/com/android/email/mail/store/Pop3Store.java b/src/com/android/email/mail/store/Pop3Store.java index c35c02835..cb959fdd0 100644 --- a/src/com/android/email/mail/store/Pop3Store.java +++ b/src/com/android/email/mail/store/Pop3Store.java @@ -78,10 +78,8 @@ public class Pop3Store extends Store { HostAuth recvAuth = account.getOrCreateHostAuthRecv(context); mTransport = new MailTransport(context, "POP3", recvAuth); String[] userInfoParts = recvAuth.getLogin(); - if (userInfoParts != null) { - mUsername = userInfoParts[0]; - mPassword = userInfoParts[1]; - } + mUsername = userInfoParts[0]; + mPassword = userInfoParts[1]; } /** diff --git a/src/com/android/email/mail/transport/ExchangeSender.java b/src/com/android/email/mail/transport/ExchangeSender.java deleted file mode 100644 index 87ba8124b..000000000 --- a/src/com/android/email/mail/transport/ExchangeSender.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2009 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.mail.transport; - -import com.android.email.mail.Sender; -import com.android.emailcommon.mail.MessagingException; -import com.android.emailcommon.provider.Account; - -import android.content.Context; - -/** - * Our Exchange service does not use the sender/store model. This class exists for exactly one - * purpose, which is to return "null" for getSettingActivityClass(). - */ -public class ExchangeSender extends Sender { - - /** - * Factory method. - */ - public static Sender newInstance(Account account, Context context) throws MessagingException { - return new ExchangeSender(context, account); - } - - private ExchangeSender(Context context, Account account) { - } - - @Override - public void close() { - } - - @Override - public void open() { - } - - @Override - public void sendMessage(long messageId) { - } - - /** - * Get class of SettingActivity for this Sender class. - * @return Activity class that has class method actionEditOutgoingSettings(), or null if - * outgoing settings should not be presented (e.g. they're handled by the incoming settings - * screen). - */ - @Override - public Class<? extends android.app.Activity> getSettingActivityClass() { - return null; - } - -} diff --git a/src/com/android/email/mail/transport/SmtpSender.java b/src/com/android/email/mail/transport/SmtpSender.java index 539ba190e..c57f78cba 100644 --- a/src/com/android/email/mail/transport/SmtpSender.java +++ b/src/com/android/email/mail/transport/SmtpSender.java @@ -69,10 +69,8 @@ public class SmtpSender extends Sender { HostAuth sendAuth = account.getOrCreateHostAuthSend(context); mTransport = new MailTransport(context, "SMTP", sendAuth); String[] userInfoParts = sendAuth.getLogin(); - if (userInfoParts != null) { - mUsername = userInfoParts[0]; - mPassword = userInfoParts[1]; - } + mUsername = userInfoParts[0]; + mPassword = userInfoParts[1]; Credential cred = sendAuth.getCredential(context); if (cred != null) { mUseOAuth = true; diff --git a/src/com/android/email/service/AuthenticatorService.java b/src/com/android/email/service/AuthenticatorService.java index 2c93c4ba1..36f3222a0 100644 --- a/src/com/android/email/service/AuthenticatorService.java +++ b/src/com/android/email/service/AuthenticatorService.java @@ -16,7 +16,7 @@ package com.android.email.service; -import com.android.email.activity.setup.AccountSetupBasics; +import com.android.email.activity.setup.AccountSetupFinal; import com.android.emailcommon.provider.EmailContent; import android.accounts.AbstractAccountAuthenticator; @@ -103,7 +103,7 @@ public class AuthenticatorService extends Service { } else { Bundle b = new Bundle(); Intent intent = - AccountSetupBasics.actionGetCreateAccountIntent(AuthenticatorService.this, + AccountSetupFinal.actionGetCreateAccountIntent(AuthenticatorService.this, accountType); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); b.putParcelable(AccountManager.KEY_INTENT, intent); diff --git a/src/com/android/email/service/EasTestAuthenticatorService.java b/src/com/android/email/service/EasTestAuthenticatorService.java index cdb213fac..c8d853b93 100644 --- a/src/com/android/email/service/EasTestAuthenticatorService.java +++ b/src/com/android/email/service/EasTestAuthenticatorService.java @@ -27,7 +27,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; -import com.android.email.activity.setup.AccountSetupBasics; +import com.android.email.activity.setup.AccountSetupFinal; /** * Anauthenticator service for reconciliation tests; it simply adds the account to AccountManager @@ -67,7 +67,7 @@ public class EasTestAuthenticatorService extends Service { } else { Bundle b = new Bundle(); Intent intent = - AccountSetupBasics.actionGetCreateAccountIntent( + AccountSetupFinal.actionGetCreateAccountIntent( EasTestAuthenticatorService.this, accountType); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); b.putParcelable(AccountManager.KEY_INTENT, intent); |