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