replicant-packages_apps_Email/src/com/android/email/activity/setup/AccountCheckSettingsFragmen...

543 lines
22 KiB
Java

/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity.setup;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import com.android.email.R;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.HostAuthCompat;
import com.android.emailcommon.utility.Utility;
import com.android.mail.utils.LogUtils;
/**
* Check incoming or outgoing settings, or perform autodiscovery.
*
* There are three components that work together. 1. This fragment is retained and non-displayed,
* and controls the overall process. 2. An AsyncTask that works with the stores/services to
* check the accounts settings. 3. A stateless progress dialog (which will be recreated on
* orientation changes).
*
* There are also two lightweight error dialogs which are used for notification of terminal
* conditions.
*/
public class AccountCheckSettingsFragment extends Fragment {
public final static String TAG = "AccountCheckStgFrag";
// State
private final static int STATE_START = 0;
private final static int STATE_CHECK_AUTODISCOVER = 1;
private final static int STATE_CHECK_INCOMING = 2;
private final static int STATE_CHECK_OUTGOING = 3;
private final static int STATE_CHECK_OK = 4; // terminal
private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal
private final static int STATE_CHECK_ERROR = 6; // terminal
private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal
private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal
private int mState = STATE_START;
// Args
private final static String ARGS_MODE = "mode";
private int mMode;
// Support for UI
private boolean mAttached;
private boolean mPaused = false;
private MessagingException mProgressException;
// Support for AsyncTask and account checking
AccountCheckTask mAccountCheckTask;
// 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) */
public final static int AUTODISCOVER_NO_DATA = 1;
/** AutoDiscover reported authentication error */
public final static int AUTODISCOVER_AUTHENTICATION = 2;
/**
* Callback interface for any target or activity doing account check settings
*/
public interface Callback {
/**
* Called when CheckSettings completed
*/
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
*/
void onCheckSettingsAutoDiscoverComplete(int result);
}
// Public no-args constructor needed for fragment re-instantiation
public AccountCheckSettingsFragment() {}
/**
* Create a retained, invisible fragment that checks accounts
*
* @param mode incoming or outgoing
*/
public static AccountCheckSettingsFragment newInstance(int mode) {
final AccountCheckSettingsFragment f = new AccountCheckSettingsFragment();
final Bundle b = new Bundle(1);
b.putInt(ARGS_MODE, mode);
f.setArguments(b);
return f;
}
/**
* Fragment initialization. Because we never implement onCreateView, and call
* setRetainInstance here, this creates an invisible, persistent, "worker" fragment.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mMode = getArguments().getInt(ARGS_MODE);
}
/**
* This is called when the Fragment's Activity is ready to go, after
* its content view has been installed; it is called both after
* the initial fragment creation and after the fragment is re-attached
* to a new activity.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAttached = true;
// If this is the first time, start the AsyncTask
if (mAccountCheckTask == null) {
final SetupDataFragment.SetupDataContainer container =
(SetupDataFragment.SetupDataContainer) getActivity();
// TODO: don't pass in the whole SetupDataFragment
mAccountCheckTask = (AccountCheckTask)
new AccountCheckTask(getActivity().getApplicationContext(), this, mMode,
container.getSetupData())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
/**
* When resuming, restart the progress/error UI if necessary by re-reporting previous values
*/
@Override
public void onResume() {
super.onResume();
mPaused = false;
if (mState != STATE_START) {
reportProgress(mState, mProgressException);
}
}
@Override
public void onPause() {
super.onPause();
mPaused = true;
}
/**
* This is called when the fragment is going away. It is NOT called
* when the fragment is being propagated between activity instances.
*/
@Override
public void onDestroy() {
super.onDestroy();
if (mAccountCheckTask != null) {
Utility.cancelTaskInterrupt(mAccountCheckTask);
mAccountCheckTask = null;
}
}
/**
* This is called right before the fragment is detached from its current activity instance.
* All reporting and callbacks are halted until we reattach.
*/
@Override
public void onDetach() {
super.onDetach();
mAttached = false;
}
/**
* The worker (AsyncTask) will call this (in the UI thread) to report progress. If we are
* attached to an activity, update the progress immediately; If not, simply hold the
* progress for later.
* @param newState The new progress state being reported
*/
private void reportProgress(int newState, MessagingException ex) {
mState = newState;
mProgressException = ex;
// If we are attached, create, recover, and/or update the dialog
if (mAttached && !mPaused) {
final FragmentManager fm = getFragmentManager();
switch (newState) {
case STATE_CHECK_OK:
// immediately terminate, clean up, and report back
getCallbackTarget().onCheckSettingsComplete();
break;
case STATE_CHECK_SHOW_SECURITY:
// 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:
// 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;
// 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
CheckSettingsProgressDialogFragment checkingDialog =
(CheckSettingsProgressDialogFragment)
fm.findFragmentByTag(CheckSettingsProgressDialogFragment.TAG);
if (checkingDialog != null) {
checkingDialog.updateProgress(mState);
}
break;
}
}
}
/**
* Find the callback target, either a target fragment or the activity
*/
private Callback getCallbackTarget() {
final Fragment target = getTargetFragment();
if (target instanceof Callback) {
return (Callback) target;
}
Activity activity = getActivity();
if (activity instanceof Callback) {
return (Callback) activity;
}
throw new IllegalStateException();
}
/**
* This exception class is used to report autodiscover results via the reporting mechanism.
*/
public static class AutoDiscoverResults extends MessagingException {
public final HostAuth mHostAuth;
/**
* @param authenticationError true if auth failure, false for result (or no response)
* @param hostAuth null for "no autodiscover", non-null for server info to return
*/
public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) {
super(null);
if (authenticationError) {
mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED;
} else {
mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT;
}
mHostAuth = hostAuth;
}
}
/**
* This AsyncTask does the actual account checking
*
* TODO: It would be better to remove the UI complete from here (the exception->string
* conversions).
*/
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 mCheckPassword;
final String mCheckEmail;
/**
* Create task and parameterize it
* @param context application context object
* @param mode bits request operations
* @param setupData {@link SetupDataFragment} holding values to be checked
*/
public AccountCheckTask(Context context, AccountCheckSettingsFragment callback, int mode,
SetupDataFragment setupData) {
mContext = context;
mCallback = callback;
mMode = mode;
mSetupData = setupData;
mAccount = setupData.getAccount();
if (mAccount.mHostAuthRecv != null) {
mStoreHost = mAccount.mHostAuthRecv.mAddress;
mCheckPassword = mAccount.mHostAuthRecv.mPassword;
} else {
mStoreHost = null;
mCheckPassword = null;
}
mCheckEmail = mAccount.mEmailAddress;
}
@Override
protected MessagingException doInBackground(Void... params) {
try {
if ((mMode & SetupDataFragment.CHECK_AUTODISCOVER) != 0) {
if (isCancelled()) return null;
LogUtils.d(Logging.LOG_TAG, "Begin auto-discover for %s", mCheckEmail);
publishProgress(STATE_CHECK_AUTODISCOVER);
mSetupData.setAutodiscover(false);
final Store store = Store.getInstance(mAccount, mContext);
final Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword);
// Result will be one of:
// null: remote exception - proceed to manual setup
// MessagingException.AUTHENTICATION_FAILED: username/password rejected
// Other error: proceed to manual setup
// No error: return autodiscover results
if (result == null) {
return new AutoDiscoverResults(false, null);
}
int errorCode = result.getInt(
EmailServiceProxy.AUTO_DISCOVER_BUNDLE_MESSAGING_ERROR_CODE);
if (errorCode == MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED) {
return new AutoDiscoverResults(true, null);
} else if (errorCode != MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT) {
return new AutoDiscoverResults(false, null);
} else {
final HostAuthCompat hostAuthCompat =
result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH);
Account account = mSetupData.getAccount();
if (hostAuthCompat != null) {
account.mHostAuthRecv = hostAuthCompat.toHostAuth();
}
mSetupData.setAutodiscover(true);
return new AutoDiscoverResults(false, account.mHostAuthRecv);
}
}
// Check Incoming Settings
if ((mMode & SetupDataFragment.CHECK_INCOMING) != 0) {
if (isCancelled()) return null;
LogUtils.d(Logging.LOG_TAG, "Begin check of incoming email settings");
publishProgress(STATE_CHECK_INCOMING);
final Store store = Store.getInstance(mAccount, mContext);
final Bundle bundle = store.checkSettings();
if (bundle == null) {
return new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION);
}
mAccount.mProtocolVersion = bundle.getString(
EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION);
int resultCode = bundle.getInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
final String redirectAddress = bundle.getString(
EmailServiceProxy.VALIDATE_BUNDLE_REDIRECT_ADDRESS, null);
if (redirectAddress != null) {
mAccount.mHostAuthRecv.mAddress = redirectAddress;
}
// Only show "policies required" if this is a new account setup
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED &&
mAccount.isSaved()) {
resultCode = MessagingException.NO_ERROR;
}
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
mSetupData.setPolicy((Policy)bundle.getParcelable(
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
return new MessagingException(resultCode, mStoreHost);
} else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) {
final Policy policy = bundle.getParcelable(
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET);
final String unsupported = policy.mProtocolPoliciesUnsupported;
final String[] data =
unsupported.split("" + Policy.POLICY_STRING_DELIMITER);
return new MessagingException(resultCode, mStoreHost, data);
} else if (resultCode != MessagingException.NO_ERROR) {
final String errorMessage;
errorMessage = bundle.getString(
EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
return new MessagingException(resultCode, errorMessage);
}
// Save account capabilities
mAccount.mCapabilities = bundle.getInt(
EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, 0);
}
final EmailServiceInfo info;
if (mAccount.mHostAuthRecv != null) {
final String protocol = mAccount.mHostAuthRecv.mProtocol;
info = EmailServiceUtils
.getServiceInfo(mContext, protocol);
} else {
info = null;
}
// Check Outgoing Settings
if ((info == null || info.usesSmtp) &&
(mMode & SetupDataFragment.CHECK_OUTGOING) != 0) {
if (isCancelled()) return null;
LogUtils.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
publishProgress(STATE_CHECK_OUTGOING);
final Sender sender = Sender.getInstance(mContext, mAccount);
sender.close();
sender.open();
sender.close();
}
// If we reached the end, we completed the check(s) successfully
return null;
} catch (final MessagingException me) {
// Some of the legacy account checkers return errors by throwing MessagingException,
// which we catch and return here.
return me;
}
}
/**
* Progress reports (runs in UI thread). This should be used for real progress only
* (not for errors).
*/
@Override
protected void onProgressUpdate(Integer... progress) {
if (isCancelled()) return;
mCallback.reportProgress(progress[0], null);
}
/**
* Result handler (runs in UI thread).
*
* AutoDiscover authentication errors are handled a bit differently than the
* other errors; If encountered, we display the error dialog, but we return with
* a different callback used only for AutoDiscover.
*
* @param result null for a successful check; exception for various errors
*/
@Override
protected void onPostExecute(MessagingException result) {
if (isCancelled()) return;
if (result == null) {
mCallback.reportProgress(STATE_CHECK_OK, null);
} else {
int progressState = STATE_CHECK_ERROR;
final int exceptionType = result.getExceptionType();
switch (exceptionType) {
// NOTE: AutoDiscover reports have their own reporting state, handle differently
// from the other exception types
case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED:
progressState = STATE_AUTODISCOVER_AUTH_DIALOG;
break;
case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT:
progressState = STATE_AUTODISCOVER_RESULT;
break;
// NOTE: Security policies required has its own report state, handle it a bit
// differently from the other exception types.
case MessagingException.SECURITY_POLICIES_REQUIRED:
progressState = STATE_CHECK_SHOW_SECURITY;
break;
}
mCallback.reportProgress(progressState, result);
}
}
}
/**
* 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 STATE_START:
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;
}
if (stringId != 0) {
return context.getString(stringId);
} else {
return null;
}
}
/**
* Convert mode to initial progress
*/
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;
}
return STATE_START;
}
}