AccountCheckSettingsFragment.java revision 31d9acbf0623872f9d4a2b3210b5970854b654c7
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity.setup;
18
19import com.android.email.R;
20import com.android.email.mail.Sender;
21import com.android.email.mail.Store;
22import com.android.emailcommon.Logging;
23import com.android.emailcommon.mail.MessagingException;
24import com.android.emailcommon.provider.EmailContent.Account;
25import com.android.emailcommon.provider.EmailContent.HostAuth;
26import com.android.emailcommon.service.EmailServiceProxy;
27import com.android.emailcommon.service.PolicySet;
28import com.android.emailcommon.utility.Utility;
29
30import android.app.Activity;
31import android.app.AlertDialog;
32import android.app.Dialog;
33import android.app.DialogFragment;
34import android.app.Fragment;
35import android.app.FragmentManager;
36import android.app.ProgressDialog;
37import android.content.Context;
38import android.content.DialogInterface;
39import android.os.AsyncTask;
40import android.os.Bundle;
41import android.util.Log;
42
43/**
44 * Check incoming or outgoing settings, or perform autodiscovery.
45 *
46 * There are three components that work together.  1. This fragment is retained and non-displayed,
47 * and controls the overall process.  2. An AsyncTask that works with the stores/services to
48 * check the accounts settings.  3. A stateless progress dialog (which will be recreated on
49 * orientation changes).
50 *
51 * There are also two lightweight error dialogs which are used for notification of terminal
52 * conditions.
53 */
54public class AccountCheckSettingsFragment extends Fragment {
55
56    public final static String TAG = "AccountCheckSettingsFragment";
57
58    // Debugging flags - for debugging the UI
59    // If true, use a "fake" account check cycle
60    private static final boolean DEBUG_FAKE_CHECK_CYCLE = false;            // DO NOT CHECK IN TRUE
61    // If true, use fake check cycle, return failure
62    private static final boolean DEBUG_FAKE_CHECK_ERR = false;              // DO NOT CHECK IN TRUE
63    // If true, use fake check cycle, return "security required"
64    private static final boolean DEBUG_FORCE_SECURITY_REQUIRED = false;     // DO NOT CHECK IN TRUE
65
66    // State
67    private final static int STATE_START = 0;
68    private final static int STATE_CHECK_AUTODISCOVER = 1;
69    private final static int STATE_CHECK_INCOMING = 2;
70    private final static int STATE_CHECK_OUTGOING = 3;
71    private final static int STATE_CHECK_OK = 4;                    // terminal
72    private final static int STATE_CHECK_SHOW_SECURITY = 5;         // terminal
73    private final static int STATE_CHECK_ERROR = 6;                 // terminal
74    private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7;    // terminal
75    private final static int STATE_AUTODISCOVER_RESULT = 8;         // terminal
76    private int mState = STATE_START;
77
78    // Support for UI
79    private boolean mAttached;
80    private CheckingDialog mCheckingDialog;
81    private int mErrorStringId;
82    private String mErrorMessage;
83    private HostAuth mAutoDiscoverResult;
84
85    // Support for AsyncTask and account checking
86    AccountCheckTask mAccountCheckTask;
87
88    // Result codes returned by onCheckSettingsComplete.
89    /** Check settings returned successfully */
90    public final static int CHECK_SETTINGS_OK = 0;
91    /** Check settings failed due to connection, authentication, or other server error */
92    public final static int CHECK_SETTINGS_SERVER_ERROR = 1;
93    /** Check settings failed due to user refusing to accept security requirements */
94    public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2;
95
96    // Result codes returned by onAutoDiscoverComplete.
97    /** AutoDiscover completed successfully with server setup data */
98    public final static int AUTODISCOVER_OK = 0;
99    /** AutoDiscover completed with no data (no server or AD not supported) */
100    public final static int AUTODISCOVER_NO_DATA = 1;
101    /** AutoDiscover reported authentication error */
102    public final static int AUTODISCOVER_AUTHENTICATION = 2;
103
104    /**
105     * Callback interface for any target or activity doing account check settings
106     */
107    public interface Callbacks {
108        /**
109         * Called when CheckSettings completed
110         * @param result check settings result code - success is CHECK_SETTINGS_OK
111         */
112        public void onCheckSettingsComplete(int result);
113
114        /**
115         * Called when autodiscovery completes.
116         * @param result autodiscovery result code - success is AUTODISCOVER_OK
117         * @param hostAuth configuration data returned by AD server, or null if no data available
118         */
119        public void onAutoDiscoverComplete(int result, HostAuth hostAuth);
120    }
121
122    /**
123     * Create a retained, invisible fragment that checks accounts
124     *
125     * @param mode incoming or outgoing
126     */
127    public static AccountCheckSettingsFragment newInstance(int mode, Fragment parentFragment) {
128        AccountCheckSettingsFragment f = new AccountCheckSettingsFragment();
129        f.setTargetFragment(parentFragment, mode);
130        return f;
131    }
132
133    /**
134     * Fragment initialization.  Because we never implement onCreateView, and call
135     * setRetainInstance here, this creates an invisible, persistent, "worker" fragment.
136     */
137    @Override
138    public void onCreate(Bundle savedInstanceState) {
139        super.onCreate(savedInstanceState);
140        setRetainInstance(true);
141    }
142
143    /**
144     * This is called when the Fragment's Activity is ready to go, after
145     * its content view has been installed; it is called both after
146     * the initial fragment creation and after the fragment is re-attached
147     * to a new activity.
148     */
149    @Override
150    public void onActivityCreated(Bundle savedInstanceState) {
151        super.onActivityCreated(savedInstanceState);
152        mAttached = true;
153
154        // If this is the first time, start the AsyncTask
155        if (mAccountCheckTask == null) {
156            int checkMode = getTargetRequestCode();
157            Account checkAccount = SetupData.getAccount();
158            mAccountCheckTask = (AccountCheckTask)
159                    new AccountCheckTask(checkMode, checkAccount)
160                    .execute();
161        }
162    }
163
164    /**
165     * When resuming, restart the progress/error UI if necessary by re-reporting previous values
166     */
167    @Override
168    public void onResume() {
169        super.onResume();
170
171        if (mState != STATE_START) {
172            reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult);
173        }
174    }
175
176    /**
177     * This is called when the fragment is going away.  It is NOT called
178     * when the fragment is being propagated between activity instances.
179     */
180    @Override
181    public void onDestroy() {
182        super.onDestroy();
183        Utility.cancelTaskInterrupt(mAccountCheckTask);
184        mAccountCheckTask = null;
185    }
186
187    /**
188     * This is called right before the fragment is detached from its current activity instance.
189     * All reporting and callbacks are halted until we reattach.
190     */
191    @Override
192    public void onDetach() {
193        super.onDetach();
194        mAttached = false;
195    }
196
197    /**
198     * The worker (AsyncTask) will call this (in the UI thread) to report progress.  If we are
199     * attached to an activity, update the progress immediately;  If not, simply hold the
200     * progress for later.
201     * @param newState The new progress state being reported
202     * @param errorStringId Resource Id of an error string to display
203     * @param errorMessage Additional string to insert if the resource string takes a parameter.
204     * @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server
205     */
206    public void reportProgress(int newState, int errorStringId, String errorMessage,
207            HostAuth autoDiscoverResult) {
208        mState = newState;
209        mErrorStringId = errorStringId;
210        mErrorMessage = errorMessage;
211        mAutoDiscoverResult = autoDiscoverResult;
212
213        // If we are attached, create, recover, and/or update the dialog
214        if (mAttached) {
215            FragmentManager fm = getFragmentManager();
216
217            switch (newState) {
218                case STATE_CHECK_OK:
219                    // immediately terminate, clean up, and report back
220                    // 1. get rid of progress dialog (if any)
221                    recoverAndDismissCheckingDialog();
222                    // 2. exit self
223                    fm.popBackStack();
224                    // 3. report OK back to target fragment or activity
225                    getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK);
226                    break;
227                case STATE_CHECK_SHOW_SECURITY:
228                    // 1. get rid of progress dialog (if any)
229                    recoverAndDismissCheckingDialog();
230                    // 2. launch the error dialog, if needed
231                    if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) {
232                        SecurityRequiredDialog securityRequiredDialog =
233                                SecurityRequiredDialog.newInstance(this, mErrorMessage);
234                        fm.beginTransaction()
235                                .add(securityRequiredDialog, SecurityRequiredDialog.TAG)
236                                .commit();
237                    }
238                    break;
239                case STATE_CHECK_ERROR:
240                case STATE_AUTODISCOVER_AUTH_DIALOG:
241                    // 1. get rid of progress dialog (if any)
242                    recoverAndDismissCheckingDialog();
243                    // 2. launch the error dialog, if needed
244                    if (fm.findFragmentByTag(ErrorDialog.TAG) == null) {
245                        ErrorDialog errorDialog =
246                                ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage);
247                        fm.beginTransaction()
248                                .add(errorDialog, ErrorDialog.TAG)
249                                .commit();
250                    }
251                    break;
252                case STATE_AUTODISCOVER_RESULT:
253                    // 1. get rid of progress dialog (if any)
254                    recoverAndDismissCheckingDialog();
255                    // 2. exit self
256                    fm.popBackStack();
257                    // 3. report back to target fragment or activity
258                    getCallbackTarget().onAutoDiscoverComplete(
259                            (mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA,
260                                    mAutoDiscoverResult);
261                    break;
262                default:
263                    // Display a normal progress message
264                    mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG);
265
266                    if (mCheckingDialog == null) {
267                        mCheckingDialog = CheckingDialog.newInstance(this, mState);
268                        fm.beginTransaction()
269                                .add(mCheckingDialog, CheckingDialog.TAG)
270                                .commit();
271                    } else {
272                        mCheckingDialog.updateProgress(mState);
273                    }
274                    break;
275            }
276        }
277    }
278
279    /**
280     * Find the callback target, either a target fragment or the activity
281     */
282    private Callbacks getCallbackTarget() {
283        Fragment target = getTargetFragment();
284        if (target instanceof Callbacks) {
285            return (Callbacks) target;
286        }
287        Activity activity = getActivity();
288        if (activity instanceof Callbacks) {
289            return (Callbacks) activity;
290        }
291        throw new IllegalStateException();
292    }
293
294    /**
295     * Recover and dismiss the progress dialog fragment
296     */
297    private void recoverAndDismissCheckingDialog() {
298        if (mCheckingDialog == null) {
299            mCheckingDialog = (CheckingDialog)
300                    getFragmentManager().findFragmentByTag(CheckingDialog.TAG);
301        }
302        if (mCheckingDialog != null) {
303            mCheckingDialog.dismiss();
304            mCheckingDialog = null;
305        }
306    }
307
308    /**
309     * This is called when the user clicks "cancel" on the progress dialog.  Shuts everything
310     * down and dismisses everything.
311     * This should cause us to remain in the current screen (not accepting the settings)
312     */
313    private void onCheckingDialogCancel() {
314        // 1. kill the checker
315        Utility.cancelTaskInterrupt(mAccountCheckTask);
316        mAccountCheckTask = null;
317        // 2. kill self with no report - this is "cancel"
318        getFragmentManager().popBackStack();
319    }
320
321    /**
322     * This is called when the user clicks "edit" from the error dialog.  The error dialog
323     * should have already dismissed itself.
324     * Depending on the context, the target will remain in the current activity (e.g. editing
325     * settings) or return to its own parent (e.g. enter new credentials).
326     */
327    private void onErrorDialogEditButton() {
328        Callbacks callbackTarget = getCallbackTarget();
329        if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) {
330            // report auth error to target fragment or activity
331            callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null);
332        } else {
333            // report check settings failure to target fragment or activity
334            callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR);
335        }
336        getFragmentManager().popBackStack();
337    }
338
339    /**
340     * This is called when the user clicks "ok" or "cancel" on the "security required" dialog.
341     * Shuts everything down and dismisses everything, and reports the result appropriately.
342     */
343    private void onSecurityRequiredDialogResultOk(boolean okPressed) {
344        // 1. handle OK/cancel - notify that security is OK and we can proceed
345        Callbacks callbackTarget = getCallbackTarget();
346        callbackTarget.onCheckSettingsComplete(
347                okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY);
348
349        // 2. kill self if not already killed by callback
350        FragmentManager fm = getFragmentManager();
351        if (fm != null) {
352            fm.popBackStack();
353        }
354    }
355
356    /**
357     * This exception class is used to report autodiscover results via the reporting mechanism.
358     */
359    public static class AutoDiscoverResults extends MessagingException {
360        public final HostAuth mHostAuth;
361
362        /**
363         * @param authenticationError true if auth failure, false for result (or no response)
364         * @param hostAuth null for "no autodiscover", non-null for server info to return
365         */
366        public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) {
367            super(null);
368            if (authenticationError) {
369                mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED;
370            } else {
371                mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT;
372            }
373            mHostAuth = hostAuth;
374        }
375    }
376
377    /**
378     * This AsyncTask does the actual account checking
379     *
380     * TODO: It would be better to remove the UI complete from here (the exception->string
381     * conversions).
382     */
383    private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> {
384
385        final Context mContext;
386        final int mMode;
387        final String mStoreUri;
388        final String mStoreHost;
389        final String mSenderUri;
390        final String mCheckEmail;
391        final String mCheckPassword;
392
393        /**
394         * Create task and parameterize it
395         * @param mode bits request operations
396         * @param checkAccount account holding values to be checked
397         */
398        public AccountCheckTask(int mode, Account checkAccount) {
399            mContext = getActivity().getApplicationContext();
400            mMode = mode;
401            mStoreUri = checkAccount.getStoreUri(mContext);
402            mStoreHost = checkAccount.mHostAuthRecv.mAddress;
403            mSenderUri = checkAccount.getSenderUri(mContext);
404            mCheckEmail = checkAccount.mEmailAddress;
405            mCheckPassword = checkAccount.mHostAuthRecv.mPassword;
406        }
407
408        @Override
409        protected MessagingException doInBackground(Void... params) {
410            if (DEBUG_FAKE_CHECK_CYCLE) {
411                return fakeChecker();
412            }
413
414            try {
415                if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
416                    if (isCancelled()) return null;
417                    publishProgress(STATE_CHECK_AUTODISCOVER);
418                    Log.d(Logging.LOG_TAG, "Begin auto-discover for " + mCheckEmail);
419                    Store store = Store.getInstance(mStoreUri, mContext, null);
420                    Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword);
421                    // Result will be one of:
422                    //  null: remote exception - proceed to manual setup
423                    //  MessagingException.AUTHENTICATION_FAILED: username/password rejected
424                    //  Other error: proceed to manual setup
425                    //  No error: return autodiscover results
426                    if (result == null) {
427                        return new AutoDiscoverResults(false, null);
428                    }
429                    int errorCode =
430                            result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
431                    if (errorCode == MessagingException.AUTHENTICATION_FAILED) {
432                        return new AutoDiscoverResults(true, null);
433                    } else if (errorCode != MessagingException.NO_ERROR) {
434                        return new AutoDiscoverResults(false, null);
435                    } else {
436                        HostAuth serverInfo = (HostAuth)
437                            result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH);
438                        return new AutoDiscoverResults(false, serverInfo);
439                    }
440                }
441
442                // Check Incoming Settings
443                if ((mMode & SetupData.CHECK_INCOMING) != 0) {
444                    if (isCancelled()) return null;
445                    Log.d(Logging.LOG_TAG, "Begin check of incoming email settings");
446                    publishProgress(STATE_CHECK_INCOMING);
447                    Store store = Store.getInstance(mStoreUri, mContext, null);
448                    Bundle bundle = store.checkSettings();
449                    int resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
450                    if (bundle != null) {
451                        resultCode = bundle.getInt(
452                                EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
453                    }
454                    if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
455                        SetupData.setPolicySet((PolicySet)bundle.getParcelable(
456                                EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
457                        return new MessagingException(
458                                MessagingException.SECURITY_POLICIES_REQUIRED, mStoreHost);
459                    }
460                    if (resultCode != MessagingException.NO_ERROR) {
461                        String errorMessage =
462                            bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
463                        return new MessagingException(resultCode, errorMessage);
464                    }
465                }
466
467                // Check Outgoing Settings
468                if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
469                    if (isCancelled()) return null;
470                    Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
471                    publishProgress(STATE_CHECK_OUTGOING);
472                    Sender sender = Sender.getInstance(mContext, mSenderUri);
473                    sender.close();
474                    sender.open();
475                    sender.close();
476                }
477
478                // If we reached the end, we completed the check(s) successfully
479                return null;
480            } catch (final MessagingException me) {
481                // Some of the legacy account checkers return errors by throwing MessagingException,
482                // which we catch and return here.
483                return me;
484            }
485        }
486
487        /**
488         * Dummy background worker, for testing UI only.
489         */
490        private MessagingException fakeChecker() {
491            // Dummy:  Publish a series of progress setups, 2 sec delays between them;
492            // then return "ok" (null)
493            final int DELAY = 2*1000;
494            if (isCancelled()) return null;
495            if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
496                publishProgress(STATE_CHECK_AUTODISCOVER);
497                try {
498                    Thread.sleep(DELAY);
499                } catch (InterruptedException e) { }
500                if (DEBUG_FAKE_CHECK_ERR) {
501                    return new MessagingException(MessagingException.AUTHENTICATION_FAILED);
502                }
503                // Return "real" AD results
504                HostAuth serverInfo = new HostAuth();
505                serverInfo.setStoreUri("eas://user:password@testserver.com");
506                return new AutoDiscoverResults(false, serverInfo);
507            }
508            if (isCancelled()) return null;
509            if ((mMode & SetupData.CHECK_INCOMING) != 0) {
510                publishProgress(STATE_CHECK_INCOMING);
511                try {
512                    Thread.sleep(DELAY);
513                } catch (InterruptedException e) { }
514                if (DEBUG_FAKE_CHECK_ERR) {
515                    return new MessagingException(MessagingException.IOERROR);
516                } else if (DEBUG_FORCE_SECURITY_REQUIRED) {
517                    return new MessagingException(
518                            MessagingException.SECURITY_POLICIES_REQUIRED);
519                }
520            }
521            if (isCancelled()) return null;
522            if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
523                publishProgress(STATE_CHECK_OUTGOING);
524                try {
525                    Thread.sleep(DELAY);
526                } catch (InterruptedException e) { }
527                if (DEBUG_FAKE_CHECK_ERR) {
528                    return new MessagingException(MessagingException.TLS_REQUIRED);
529                }
530            }
531            return null;
532        }
533
534        /**
535         * Progress reports (runs in UI thread).  This should be used for real progress only
536         * (not for errors).
537         */
538        @Override
539        protected void onProgressUpdate(Integer... progress) {
540            if (isCancelled()) return;
541            reportProgress(progress[0], 0, null, null);
542        }
543
544        /**
545         * Result handler (runs in UI thread).
546         *
547         * AutoDiscover authentication errors are handled a bit differently than the
548         * other errors;  If encountered, we display the error dialog, but we return with
549         * a different callback used only for AutoDiscover.
550         *
551         * @param result null for a successful check;  exception for various errors
552         */
553        @Override
554        protected void onPostExecute(MessagingException result) {
555            if (isCancelled()) return;
556            if (result == null) {
557                reportProgress(STATE_CHECK_OK, 0, null, null);
558            } else {
559                int progressState = STATE_CHECK_ERROR;
560                int exceptionType = result.getExceptionType();
561                String message = result.getMessage();
562                HostAuth hostAuth = null;
563                int id = 0;
564
565                switch (exceptionType) {
566                    // NOTE: AutoDiscover reports have their own reporting state, handle differently
567                    // from the other exception types
568                    case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED:
569                        id = (message == null)
570                            ? R.string.account_setup_failed_dlg_auth_message
571                            : R.string.account_setup_failed_dlg_auth_message_fmt;
572                        progressState = STATE_AUTODISCOVER_AUTH_DIALOG;
573                        break;
574                    case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT:
575                        hostAuth = ((AutoDiscoverResults)result).mHostAuth;
576                        progressState = STATE_AUTODISCOVER_RESULT;
577                        break;
578
579                    // NOTE: Security policies required has its own report state, handle it a bit
580                    // differently from the other exception types.
581                    case MessagingException.SECURITY_POLICIES_REQUIRED:
582                        progressState = STATE_CHECK_SHOW_SECURITY;
583                        break;
584
585                    // The remaining exception types are handled by setting the state to
586                    // STATE_CHECK_ERROR (above, default) and conversion to specific error strings.
587                    case MessagingException.CERTIFICATE_VALIDATION_ERROR:
588                        id = (message == null)
589                            ? R.string.account_setup_failed_dlg_certificate_message
590                            : R.string.account_setup_failed_dlg_certificate_message_fmt;
591                        break;
592                    case MessagingException.AUTHENTICATION_FAILED:
593                        id = (message == null)
594                            ? R.string.account_setup_failed_dlg_auth_message
595                            : R.string.account_setup_failed_dlg_auth_message_fmt;
596                        break;
597                    case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR:
598                        id = R.string.account_setup_failed_check_credentials_message;
599                        break;
600                    case MessagingException.IOERROR:
601                        id = R.string.account_setup_failed_ioerror;
602                        break;
603                    case MessagingException.TLS_REQUIRED:
604                        id = R.string.account_setup_failed_tls_required;
605                        break;
606                    case MessagingException.AUTH_REQUIRED:
607                        id = R.string.account_setup_failed_auth_required;
608                        break;
609                    case MessagingException.SECURITY_POLICIES_UNSUPPORTED:
610                        id = R.string.account_setup_failed_security_policies_unsupported;
611                        break;
612                    case MessagingException.PROTOCOL_VERSION_UNSUPPORTED:
613                        id = R.string.account_setup_failed_protocol_unsupported;
614                        break;
615                    case MessagingException.GENERAL_SECURITY:
616                        id = R.string.account_setup_failed_security;
617                        break;
618                    default:
619                        id = (message == null)
620                                ? R.string.account_setup_failed_dlg_server_message
621                                : R.string.account_setup_failed_dlg_server_message_fmt;
622                        break;
623                }
624                reportProgress(progressState, id, message, hostAuth);
625            }
626        }
627    }
628
629    /**
630     * Simple dialog that shows progress as we work through the settings checks.
631     * This is stateless except for its UI (e.g. current strings) and can be torn down or
632     * recreated at any time without affecting the account checking progress.
633     */
634    public static class CheckingDialog extends DialogFragment {
635        public final static String TAG = "CheckProgressDialog";
636
637        // Extras for saved instance state
638        private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress";
639
640        // UI
641        private String mProgressString;
642
643        /**
644         * Create a dialog that reports progress
645         * @param progress initial progress indication
646         */
647        public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment,
648                int progress) {
649            CheckingDialog f = new CheckingDialog();
650            f.setTargetFragment(parentFragment, progress);
651            return f;
652        }
653
654        /**
655         * Update the progress of an existing dialog
656         * @param progress latest progress to be displayed
657         */
658        public void updateProgress(int progress) {
659            mProgressString = getProgressString(progress);
660            AlertDialog dialog = (AlertDialog) getDialog();
661            dialog.setMessage(mProgressString);
662        }
663
664        @Override
665        public Dialog onCreateDialog(Bundle savedInstanceState) {
666            Context context = getActivity();
667            if (savedInstanceState != null) {
668                mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING);
669            }
670            if (mProgressString == null) {
671                mProgressString = getProgressString(getTargetRequestCode());
672            }
673            final AccountCheckSettingsFragment target =
674                (AccountCheckSettingsFragment) getTargetFragment();
675
676            ProgressDialog dialog = new ProgressDialog(context);
677            dialog.setIndeterminate(true);
678            dialog.setMessage(mProgressString);
679            dialog.setButton(DialogInterface.BUTTON_NEGATIVE,
680                    context.getString(R.string.cancel_action),
681                    new DialogInterface.OnClickListener() {
682                        public void onClick(DialogInterface dialog, int which) {
683                            dismiss();
684                            target.onCheckingDialogCancel();
685                        }
686                    });
687            return dialog;
688        }
689
690        /**
691         * Listen for cancellation, which can happen from places other than the
692         * negative button (e.g. touching outside the dialog), and stop the checker
693         */
694        @Override
695        public void onCancel(DialogInterface dialog) {
696            AccountCheckSettingsFragment target =
697                (AccountCheckSettingsFragment) getTargetFragment();
698            target.onCheckingDialogCancel();
699            super.onCancel(dialog);
700        }
701
702        @Override
703        public void onSaveInstanceState(Bundle outState) {
704            super.onSaveInstanceState(outState);
705            outState.putString(EXTRA_PROGRESS_STRING, mProgressString);
706        }
707
708        /**
709         * Convert progress to message
710         */
711        private String getProgressString(int progress) {
712            int stringId = 0;
713            switch (progress) {
714                case STATE_CHECK_AUTODISCOVER:
715                    stringId = R.string.account_setup_check_settings_retr_info_msg;
716                    break;
717                case STATE_CHECK_INCOMING:
718                    stringId = R.string.account_setup_check_settings_check_incoming_msg;
719                    break;
720                case STATE_CHECK_OUTGOING:
721                    stringId = R.string.account_setup_check_settings_check_outgoing_msg;
722                    break;
723            }
724            return getActivity().getString(stringId);
725        }
726    }
727
728    /**
729     * The standard error dialog.  Calls back to onErrorDialogButton().
730     */
731    public static class ErrorDialog extends DialogFragment {
732        public final static String TAG = "ErrorDialog";
733
734        // Bundle keys for arguments
735        private final static String ARGS_MESSAGE_ID = "ErrorDialog.Message.Id";
736        private final static String ARGS_MESSAGE_ARGS = "ErrorDialog.Message.Args";
737
738        public static ErrorDialog newInstance(AccountCheckSettingsFragment target,
739                int messageId, String... messageArguments) {
740            ErrorDialog fragment = new ErrorDialog();
741            Bundle arguments = new Bundle();
742            arguments.putInt(ARGS_MESSAGE_ID, messageId);
743            arguments.putStringArray(ARGS_MESSAGE_ARGS, messageArguments);
744            fragment.setArguments(arguments);
745            fragment.setTargetFragment(target, 0);
746            return fragment;
747        }
748
749        @Override
750        public Dialog onCreateDialog(Bundle savedInstanceState) {
751            final Context context = getActivity();
752            final Bundle arguments = getArguments();
753            final int messageId = arguments.getInt(ARGS_MESSAGE_ID);
754            final Object[] messageArguments = arguments.getStringArray(ARGS_MESSAGE_ARGS);
755            final AccountCheckSettingsFragment target =
756                    (AccountCheckSettingsFragment) getTargetFragment();
757
758            return new AlertDialog.Builder(context)
759                .setIconAttribute(android.R.attr.alertDialogIcon)
760                .setTitle(context.getString(R.string.account_setup_failed_dlg_title))
761                .setMessage(context.getString(messageId, messageArguments))
762                .setCancelable(true)
763                .setPositiveButton(
764                        context.getString(R.string.account_setup_failed_dlg_edit_details_action),
765                        new DialogInterface.OnClickListener() {
766                            public void onClick(DialogInterface dialog, int which) {
767                                dismiss();
768                                target.onErrorDialogEditButton();
769                            }
770                        })
771                 .create();
772        }
773
774    }
775
776    /**
777     * The "security required" error dialog.  This is presented whenever an exchange account
778     * reports that it will require security policy control, and provide the user with the
779     * opportunity to accept or deny this.
780     *
781     * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back
782     * to the target as if the settings check was "ok".  If the user clicks "cancel", calls
783     * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the
784     * same as any other failed check.)
785     */
786    public static class SecurityRequiredDialog extends DialogFragment {
787        public final static String TAG = "SecurityRequiredDialog";
788
789        // Bundle keys for arguments
790        private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName";
791
792        public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target,
793                String hostName) {
794            SecurityRequiredDialog fragment = new SecurityRequiredDialog();
795            Bundle arguments = new Bundle();
796            arguments.putString(ARGS_HOST_NAME, hostName);
797            fragment.setArguments(arguments);
798            fragment.setTargetFragment(target, 0);
799            return fragment;
800        }
801
802        @Override
803        public Dialog onCreateDialog(Bundle savedInstanceState) {
804            final Context context = getActivity();
805            final Bundle arguments = getArguments();
806            final String hostName = arguments.getString(ARGS_HOST_NAME);
807            final AccountCheckSettingsFragment target =
808                    (AccountCheckSettingsFragment) getTargetFragment();
809
810            return new AlertDialog.Builder(context)
811                .setIconAttribute(android.R.attr.alertDialogIcon)
812                .setTitle(context.getString(R.string.account_setup_security_required_title))
813                .setMessage(context.getString(
814                        R.string.account_setup_security_policies_required_fmt, hostName))
815                .setCancelable(true)
816                .setPositiveButton(
817                        context.getString(R.string.okay_action),
818                        new DialogInterface.OnClickListener() {
819                            public void onClick(DialogInterface dialog, int which) {
820                                dismiss();
821                                target.onSecurityRequiredDialogResultOk(true);
822                            }
823                        })
824                .setNegativeButton(
825                        context.getString(R.string.cancel_action),
826                        new DialogInterface.OnClickListener() {
827                            public void onClick(DialogInterface dialog, int which) {
828                                dismiss();
829                                target.onSecurityRequiredDialogResultOk(false);
830                            }
831                        })
832                 .create();
833        }
834
835    }
836
837}
838