871 lines
38 KiB
Java
871 lines
38 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 com.android.email.R;
|
|
import com.android.email.mail.Sender;
|
|
import com.android.email.mail.Store;
|
|
import com.android.emailcommon.Logging;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
import com.android.emailcommon.provider.EmailContent.Account;
|
|
import com.android.emailcommon.provider.HostAuth;
|
|
import com.android.emailcommon.provider.Policy;
|
|
import com.android.emailcommon.service.EmailServiceProxy;
|
|
import com.android.emailcommon.utility.Utility;
|
|
|
|
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 android.util.Log;
|
|
|
|
/**
|
|
* 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 = "AccountCheckSettingsFragment";
|
|
|
|
// Debugging flags - for debugging the UI
|
|
// If true, use a "fake" account check cycle
|
|
private static final boolean DEBUG_FAKE_CHECK_CYCLE = false; // DO NOT CHECK IN TRUE
|
|
// If true, use fake check cycle, return failure
|
|
private static final boolean DEBUG_FAKE_CHECK_ERR = false; // DO NOT CHECK IN TRUE
|
|
// If true, use fake check cycle, return "security required"
|
|
private static final boolean DEBUG_FORCE_SECURITY_REQUIRED = false; // DO NOT CHECK IN TRUE
|
|
|
|
// State
|
|
private final static int STATE_START = 0;
|
|
private final static int STATE_CHECK_AUTODISCOVER = 1;
|
|
private final static int STATE_CHECK_INCOMING = 2;
|
|
private final static int STATE_CHECK_OUTGOING = 3;
|
|
private final static int STATE_CHECK_OK = 4; // terminal
|
|
private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal
|
|
private final static int STATE_CHECK_ERROR = 6; // terminal
|
|
private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal
|
|
private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal
|
|
private int mState = STATE_START;
|
|
|
|
// Support for UI
|
|
private boolean mAttached;
|
|
private CheckingDialog mCheckingDialog;
|
|
private int mErrorStringId;
|
|
private String mErrorMessage;
|
|
private HostAuth mAutoDiscoverResult;
|
|
|
|
// Support for AsyncTask and account checking
|
|
AccountCheckTask mAccountCheckTask;
|
|
|
|
// Result codes returned by onCheckSettingsComplete.
|
|
/** Check settings returned successfully */
|
|
public final static int CHECK_SETTINGS_OK = 0;
|
|
/** Check settings failed due to connection, authentication, or other server error */
|
|
public final static int CHECK_SETTINGS_SERVER_ERROR = 1;
|
|
/** Check settings failed due to user refusing to accept security requirements */
|
|
public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2;
|
|
|
|
// Result codes returned by onAutoDiscoverComplete.
|
|
/** AutoDiscover completed successfully with server setup data */
|
|
public final static int AUTODISCOVER_OK = 0;
|
|
/** AutoDiscover completed with no data (no server or AD not supported) */
|
|
public final static int AUTODISCOVER_NO_DATA = 1;
|
|
/** AutoDiscover reported authentication error */
|
|
public final static int AUTODISCOVER_AUTHENTICATION = 2;
|
|
|
|
/**
|
|
* Callback interface for any target or activity doing account check settings
|
|
*/
|
|
public interface Callbacks {
|
|
/**
|
|
* Called when CheckSettings completed
|
|
* @param result check settings result code - success is CHECK_SETTINGS_OK
|
|
*/
|
|
public void onCheckSettingsComplete(int result);
|
|
|
|
/**
|
|
* Called when autodiscovery completes.
|
|
* @param result autodiscovery result code - success is AUTODISCOVER_OK
|
|
* @param hostAuth configuration data returned by AD server, or null if no data available
|
|
*/
|
|
public void onAutoDiscoverComplete(int result, HostAuth hostAuth);
|
|
}
|
|
|
|
/**
|
|
* Create a retained, invisible fragment that checks accounts
|
|
*
|
|
* @param mode incoming or outgoing
|
|
*/
|
|
public static AccountCheckSettingsFragment newInstance(int mode, Fragment parentFragment) {
|
|
AccountCheckSettingsFragment f = new AccountCheckSettingsFragment();
|
|
f.setTargetFragment(parentFragment, mode);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
int checkMode = getTargetRequestCode();
|
|
Account checkAccount = SetupData.getAccount();
|
|
mAccountCheckTask = (AccountCheckTask)
|
|
new AccountCheckTask(checkMode, checkAccount)
|
|
.execute();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When resuming, restart the progress/error UI if necessary by re-reporting previous values
|
|
*/
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
if (mState != STATE_START) {
|
|
reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
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
|
|
* @param errorStringId Resource Id of an error string to display
|
|
* @param errorMessage Additional string to insert if the resource string takes a parameter.
|
|
* @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server
|
|
*/
|
|
public void reportProgress(int newState, int errorStringId, String errorMessage,
|
|
HostAuth autoDiscoverResult) {
|
|
mState = newState;
|
|
mErrorStringId = errorStringId;
|
|
mErrorMessage = errorMessage;
|
|
mAutoDiscoverResult = autoDiscoverResult;
|
|
|
|
// If we are attached, create, recover, and/or update the dialog
|
|
if (mAttached) {
|
|
FragmentManager fm = getFragmentManager();
|
|
|
|
switch (newState) {
|
|
case STATE_CHECK_OK:
|
|
// immediately terminate, clean up, and report back
|
|
// 1. get rid of progress dialog (if any)
|
|
recoverAndDismissCheckingDialog();
|
|
// 2. exit self
|
|
fm.popBackStack();
|
|
// 3. report OK back to target fragment or activity
|
|
getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK);
|
|
break;
|
|
case STATE_CHECK_SHOW_SECURITY:
|
|
// 1. get rid of progress dialog (if any)
|
|
recoverAndDismissCheckingDialog();
|
|
// 2. launch the error dialog, if needed
|
|
if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) {
|
|
SecurityRequiredDialog securityRequiredDialog =
|
|
SecurityRequiredDialog.newInstance(this, mErrorMessage);
|
|
fm.beginTransaction()
|
|
.add(securityRequiredDialog, SecurityRequiredDialog.TAG)
|
|
.commit();
|
|
}
|
|
break;
|
|
case STATE_CHECK_ERROR:
|
|
case STATE_AUTODISCOVER_AUTH_DIALOG:
|
|
// 1. get rid of progress dialog (if any)
|
|
recoverAndDismissCheckingDialog();
|
|
// 2. launch the error dialog, if needed
|
|
if (fm.findFragmentByTag(ErrorDialog.TAG) == null) {
|
|
ErrorDialog errorDialog =
|
|
ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage);
|
|
fm.beginTransaction()
|
|
.add(errorDialog, ErrorDialog.TAG)
|
|
.commit();
|
|
}
|
|
break;
|
|
case STATE_AUTODISCOVER_RESULT:
|
|
// 1. get rid of progress dialog (if any)
|
|
recoverAndDismissCheckingDialog();
|
|
// 2. exit self
|
|
fm.popBackStack();
|
|
// 3. report back to target fragment or activity
|
|
getCallbackTarget().onAutoDiscoverComplete(
|
|
(mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA,
|
|
mAutoDiscoverResult);
|
|
break;
|
|
default:
|
|
// Display a normal progress message
|
|
mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG);
|
|
|
|
if (mCheckingDialog == null) {
|
|
mCheckingDialog = CheckingDialog.newInstance(this, mState);
|
|
fm.beginTransaction()
|
|
.add(mCheckingDialog, CheckingDialog.TAG)
|
|
.commit();
|
|
} else {
|
|
mCheckingDialog.updateProgress(mState);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the callback target, either a target fragment or the activity
|
|
*/
|
|
private Callbacks getCallbackTarget() {
|
|
Fragment target = getTargetFragment();
|
|
if (target instanceof Callbacks) {
|
|
return (Callbacks) target;
|
|
}
|
|
Activity activity = getActivity();
|
|
if (activity instanceof Callbacks) {
|
|
return (Callbacks) 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) {
|
|
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"
|
|
getFragmentManager().popBackStack();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
Callbacks callbackTarget = getCallbackTarget();
|
|
if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) {
|
|
// report auth error to target fragment or activity
|
|
callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null);
|
|
} else {
|
|
// report check settings failure to target fragment or activity
|
|
callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR);
|
|
}
|
|
// 2. kill self if not already killed by callback
|
|
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
|
|
Callbacks callbackTarget = getCallbackTarget();
|
|
callbackTarget.onCheckSettingsComplete(
|
|
okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY);
|
|
|
|
// 2. kill self if not already killed by callback
|
|
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 {
|
|
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 class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> {
|
|
|
|
final Context mContext;
|
|
final int mMode;
|
|
final Account mAccount;
|
|
final String mStoreHost;
|
|
final String mCheckEmail;
|
|
final String mCheckPassword;
|
|
|
|
/**
|
|
* Create task and parameterize it
|
|
* @param mode bits request operations
|
|
* @param checkAccount account holding values to be checked
|
|
*/
|
|
public AccountCheckTask(int mode, Account checkAccount) {
|
|
mContext = getActivity().getApplicationContext();
|
|
mMode = mode;
|
|
mAccount = checkAccount;
|
|
mStoreHost = checkAccount.mHostAuthRecv.mAddress;
|
|
mCheckEmail = checkAccount.mEmailAddress;
|
|
mCheckPassword = checkAccount.mHostAuthRecv.mPassword;
|
|
}
|
|
|
|
@Override
|
|
protected MessagingException doInBackground(Void... params) {
|
|
if (DEBUG_FAKE_CHECK_CYCLE) {
|
|
return fakeChecker();
|
|
}
|
|
|
|
try {
|
|
if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
|
|
if (isCancelled()) return null;
|
|
publishProgress(STATE_CHECK_AUTODISCOVER);
|
|
Log.d(Logging.LOG_TAG, "Begin auto-discover for " + mCheckEmail);
|
|
Store store = Store.getInstance(mAccount, mContext, null);
|
|
Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword);
|
|
// Result will be one of:
|
|
// null: remote exception - proceed to manual setup
|
|
// MessagingException.AUTHENTICATION_FAILED: username/password rejected
|
|
// Other error: proceed to manual setup
|
|
// No error: return autodiscover results
|
|
if (result == null) {
|
|
return new AutoDiscoverResults(false, null);
|
|
}
|
|
int errorCode =
|
|
result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
|
|
if (errorCode == MessagingException.AUTHENTICATION_FAILED) {
|
|
return new AutoDiscoverResults(true, null);
|
|
} else if (errorCode != MessagingException.NO_ERROR) {
|
|
return new AutoDiscoverResults(false, null);
|
|
} else {
|
|
HostAuth serverInfo = (HostAuth)
|
|
result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH);
|
|
return new AutoDiscoverResults(false, serverInfo);
|
|
}
|
|
}
|
|
|
|
// Check Incoming Settings
|
|
if ((mMode & SetupData.CHECK_INCOMING) != 0) {
|
|
if (isCancelled()) return null;
|
|
Log.d(Logging.LOG_TAG, "Begin check of incoming email settings");
|
|
publishProgress(STATE_CHECK_INCOMING);
|
|
Store store = Store.getInstance(mAccount, mContext, null);
|
|
Bundle bundle = store.checkSettings();
|
|
int resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
|
|
if (bundle != null) {
|
|
resultCode = bundle.getInt(
|
|
EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
|
|
}
|
|
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
|
|
SetupData.setPolicy((Policy)bundle.getParcelable(
|
|
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
|
|
return new MessagingException(resultCode, mStoreHost);
|
|
} else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) {
|
|
String[] data = bundle.getStringArray(
|
|
EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES);
|
|
return new MessagingException(resultCode, mStoreHost, data);
|
|
} else if (resultCode != MessagingException.NO_ERROR) {
|
|
String errorMessage =
|
|
bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
|
|
return new MessagingException(resultCode, errorMessage);
|
|
}
|
|
}
|
|
|
|
// Check Outgoing Settings
|
|
if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
|
|
if (isCancelled()) return null;
|
|
Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
|
|
publishProgress(STATE_CHECK_OUTGOING);
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dummy background worker, for testing UI only.
|
|
*/
|
|
private MessagingException fakeChecker() {
|
|
// Dummy: Publish a series of progress setups, 2 sec delays between them;
|
|
// then return "ok" (null)
|
|
final int DELAY = 2*1000;
|
|
if (isCancelled()) return null;
|
|
if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
|
|
publishProgress(STATE_CHECK_AUTODISCOVER);
|
|
try {
|
|
Thread.sleep(DELAY);
|
|
} catch (InterruptedException e) { }
|
|
if (DEBUG_FAKE_CHECK_ERR) {
|
|
return new MessagingException(MessagingException.AUTHENTICATION_FAILED);
|
|
}
|
|
// Return "real" AD results
|
|
HostAuth auth = new HostAuth();
|
|
auth.setLogin("user", "password");
|
|
auth.setConnection(Store.STORE_SCHEME_EAS, "testserver.com", 0);
|
|
return new AutoDiscoverResults(false, auth);
|
|
}
|
|
if (isCancelled()) return null;
|
|
if ((mMode & SetupData.CHECK_INCOMING) != 0) {
|
|
publishProgress(STATE_CHECK_INCOMING);
|
|
try {
|
|
Thread.sleep(DELAY);
|
|
} catch (InterruptedException e) { }
|
|
if (DEBUG_FAKE_CHECK_ERR) {
|
|
return new MessagingException(MessagingException.IOERROR);
|
|
} else if (DEBUG_FORCE_SECURITY_REQUIRED) {
|
|
return new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
|
|
}
|
|
}
|
|
if (isCancelled()) return null;
|
|
if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
|
|
publishProgress(STATE_CHECK_OUTGOING);
|
|
try {
|
|
Thread.sleep(DELAY);
|
|
} catch (InterruptedException e) { }
|
|
if (DEBUG_FAKE_CHECK_ERR) {
|
|
return new MessagingException(MessagingException.TLS_REQUIRED);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
reportProgress(progress[0], 0, null, 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) {
|
|
reportProgress(STATE_CHECK_OK, 0, null, null);
|
|
} else {
|
|
int progressState = STATE_CHECK_ERROR;
|
|
int exceptionType = result.getExceptionType();
|
|
String message = result.getMessage();
|
|
if (message != null) {
|
|
message = message.trim();
|
|
}
|
|
HostAuth hostAuth = null;
|
|
int id = 0;
|
|
|
|
switch (exceptionType) {
|
|
// NOTE: AutoDiscover reports have their own reporting state, handle differently
|
|
// from the other exception types
|
|
case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED:
|
|
id = TextUtils.isEmpty(message)
|
|
? R.string.account_setup_failed_dlg_auth_message
|
|
: R.string.account_setup_failed_dlg_auth_message_fmt;
|
|
progressState = STATE_AUTODISCOVER_AUTH_DIALOG;
|
|
break;
|
|
case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT:
|
|
hostAuth = ((AutoDiscoverResults)result).mHostAuth;
|
|
progressState = STATE_AUTODISCOVER_RESULT;
|
|
break;
|
|
|
|
// NOTE: Security policies required has its own report state, handle it a bit
|
|
// differently from the other exception types.
|
|
case MessagingException.SECURITY_POLICIES_REQUIRED:
|
|
progressState = STATE_CHECK_SHOW_SECURITY;
|
|
break;
|
|
|
|
// The remaining exception types are handled by setting the state to
|
|
// STATE_CHECK_ERROR (above, default) and conversion to specific error strings.
|
|
case MessagingException.CERTIFICATE_VALIDATION_ERROR:
|
|
id = 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 = TextUtils.isEmpty(message)
|
|
? R.string.account_setup_failed_dlg_auth_message
|
|
: R.string.account_setup_failed_dlg_auth_message_fmt;
|
|
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[])result.getExceptionData();
|
|
if (unsupportedPolicies == null) {
|
|
Log.w(TAG, "No data for unsupported policies?");
|
|
break;
|
|
}
|
|
// Build a string, concatenating policies we don't support
|
|
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;
|
|
default:
|
|
id = TextUtils.isEmpty(message)
|
|
? R.string.account_setup_failed_dlg_server_message
|
|
: R.string.account_setup_failed_dlg_server_message_fmt;
|
|
break;
|
|
}
|
|
reportProgress(progressState, id, message, hostAuth);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Create a dialog that reports progress
|
|
* @param progress initial progress indication
|
|
*/
|
|
public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment,
|
|
int progress) {
|
|
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);
|
|
AlertDialog dialog = (AlertDialog) getDialog();
|
|
dialog.setMessage(mProgressString);
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
Context context = getActivity();
|
|
if (savedInstanceState != null) {
|
|
mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING);
|
|
}
|
|
if (mProgressString == null) {
|
|
mProgressString = getProgressString(getTargetRequestCode());
|
|
}
|
|
final AccountCheckSettingsFragment target =
|
|
(AccountCheckSettingsFragment) getTargetFragment();
|
|
|
|
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() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
dismiss();
|
|
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) {
|
|
AccountCheckSettingsFragment target =
|
|
(AccountCheckSettingsFragment) getTargetFragment();
|
|
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_ID = "ErrorDialog.Message.Id";
|
|
private final static String ARGS_MESSAGE_ARGS = "ErrorDialog.Message.Args";
|
|
|
|
public static ErrorDialog newInstance(AccountCheckSettingsFragment target,
|
|
int messageId, String... messageArguments) {
|
|
ErrorDialog fragment = new ErrorDialog();
|
|
Bundle arguments = new Bundle();
|
|
arguments.putInt(ARGS_MESSAGE_ID, messageId);
|
|
arguments.putStringArray(ARGS_MESSAGE_ARGS, messageArguments);
|
|
fragment.setArguments(arguments);
|
|
fragment.setTargetFragment(target, 0);
|
|
return fragment;
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
final Context context = getActivity();
|
|
final Bundle arguments = getArguments();
|
|
final int messageId = arguments.getInt(ARGS_MESSAGE_ID);
|
|
final Object[] messageArguments = arguments.getStringArray(ARGS_MESSAGE_ARGS);
|
|
final AccountCheckSettingsFragment target =
|
|
(AccountCheckSettingsFragment) getTargetFragment();
|
|
|
|
return new AlertDialog.Builder(context)
|
|
.setIconAttribute(android.R.attr.alertDialogIcon)
|
|
.setTitle(context.getString(R.string.account_setup_failed_dlg_title))
|
|
.setMessage(context.getString(messageId, messageArguments))
|
|
.setCancelable(true)
|
|
.setPositiveButton(
|
|
context.getString(R.string.account_setup_failed_dlg_edit_details_action),
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
dismiss();
|
|
target.onErrorDialogEditButton();
|
|
}
|
|
})
|
|
.create();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* The "security required" error dialog. This is presented whenever an exchange account
|
|
* reports that it will require security policy control, and provide the user with the
|
|
* opportunity to accept or deny this.
|
|
*
|
|
* If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back
|
|
* to the target as if the settings check was "ok". If the user clicks "cancel", calls
|
|
* onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the
|
|
* same as any other failed check.)
|
|
*/
|
|
public static class SecurityRequiredDialog extends DialogFragment {
|
|
@SuppressWarnings("hiding")
|
|
public final static String TAG = "SecurityRequiredDialog";
|
|
|
|
// Bundle keys for arguments
|
|
private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName";
|
|
|
|
public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target,
|
|
String hostName) {
|
|
SecurityRequiredDialog fragment = new SecurityRequiredDialog();
|
|
Bundle arguments = new Bundle();
|
|
arguments.putString(ARGS_HOST_NAME, hostName);
|
|
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 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() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
dismiss();
|
|
target.onSecurityRequiredDialogResultOk(true);
|
|
}
|
|
})
|
|
.setNegativeButton(
|
|
context.getString(R.string.cancel_action),
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
dismiss();
|
|
target.onSecurityRequiredDialogResultOk(false);
|
|
}
|
|
})
|
|
.create();
|
|
}
|
|
|
|
}
|
|
|
|
}
|