AccountCheckSettingsFragment.java revision 992b7e7ed34334a622d8ccb1d2f692a83a15360e
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.Email;
20import com.android.email.R;
21import com.android.email.Utility;
22import com.android.email.SecurityPolicy.PolicySet;
23import com.android.email.mail.MessagingException;
24import com.android.email.mail.Sender;
25import com.android.email.mail.Store;
26import com.android.email.provider.EmailContent.Account;
27import com.android.email.provider.EmailContent.HostAuth;
28import com.android.email.service.EmailServiceProxy;
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        // if reattaching, update progress/error UI by re-reporting the previous values
164        if (mState != STATE_START) {
165            reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult);
166        }
167    }
168
169    /**
170     * This is called when the fragment is going away.  It is NOT called
171     * when the fragment is being propagated between activity instances.
172     */
173    @Override
174    public void onDestroy() {
175        super.onDestroy();
176        Utility.cancelTaskInterrupt(mAccountCheckTask);
177        mAccountCheckTask = null;
178    }
179
180    /**
181     * This is called right before the fragment is detached from its current activity instance.
182     * All reporting and callbacks are halted until we reattach.
183     */
184    @Override
185    public void onDetach() {
186        super.onDetach();
187        mAttached = false;
188    }
189
190    /**
191     * The worker (AsyncTask) will call this (in the UI thread) to report progress.  If we are
192     * attached to an activity, update the progress immediately;  If not, simply hold the
193     * progress for later.
194     * @param newState The new progress state being reported
195     * @param errorStringId Resource Id of an error string to display
196     * @param errorMessage Additional string to insert if the resource string takes a parameter.
197     * @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server
198     */
199    public void reportProgress(int newState, int errorStringId, String errorMessage,
200            HostAuth autoDiscoverResult) {
201        mState = newState;
202        mErrorStringId = errorStringId;
203        mErrorMessage = errorMessage;
204        mAutoDiscoverResult = autoDiscoverResult;
205
206        // If we are attached, create, recover, and/or update the dialog
207        if (mAttached) {
208            FragmentManager fm = getFragmentManager();
209
210            switch (newState) {
211                case STATE_CHECK_OK:
212                    // immediately terminate, clean up, and report back
213                    // 1. get rid of progress dialog (if any)
214                    recoverAndDismissCheckingDialog();
215                    // 2. exit self
216                    fm.popBackStack();
217                    // 3. report OK back to target fragment or activity
218                    getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK);
219                    break;
220                case STATE_CHECK_SHOW_SECURITY:
221                    // 1. get rid of progress dialog (if any)
222                    recoverAndDismissCheckingDialog();
223                    // 2. launch the error dialog
224                    SecurityRequiredDialog securityRequiredDialog =
225                            SecurityRequiredDialog.newInstance(this, mErrorMessage);
226                    fm.openTransaction()
227                            .add(securityRequiredDialog, SecurityRequiredDialog.TAG)
228                            .commit();
229                    break;
230                case STATE_CHECK_ERROR:
231                case STATE_AUTODISCOVER_AUTH_DIALOG:
232                    // 1. get rid of progress dialog (if any)
233                    recoverAndDismissCheckingDialog();
234                    // 2. launch the error dialog
235                    ErrorDialog errorDialog =
236                            ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage);
237                    fm.openTransaction()
238                            .add(errorDialog, ErrorDialog.TAG)
239                            .commit();
240                    break;
241                case STATE_AUTODISCOVER_RESULT:
242                    // 1. get rid of progress dialog (if any)
243                    recoverAndDismissCheckingDialog();
244                    // 2. exit self
245                    fm.popBackStack();
246                    // 3. report back to target fragment or activity
247                    getCallbackTarget().onAutoDiscoverComplete(
248                            (mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA,
249                                    mAutoDiscoverResult);
250                    break;
251                default:
252                    // Display a normal progress message
253                    mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG);
254
255                    if (mCheckingDialog == null) {
256                        mCheckingDialog = CheckingDialog.newInstance(this, mState);
257                        fm.openTransaction()
258                                .add(mCheckingDialog, CheckingDialog.TAG)
259                                .commit();
260                    } else {
261                        mCheckingDialog.updateProgress(mState);
262                    }
263                    break;
264            }
265        }
266    }
267
268    /**
269     * Find the callback target, either a target fragment or the activity
270     */
271    private Callbacks getCallbackTarget() {
272        Fragment target = getTargetFragment();
273        if (target instanceof Callbacks) {
274            return (Callbacks) target;
275        }
276        Activity activity = getActivity();
277        if (activity instanceof Callbacks) {
278            return (Callbacks) activity;
279        }
280        throw new IllegalStateException();
281    }
282
283    /**
284     * Recover and dismiss the progress dialog fragment
285     */
286    private void recoverAndDismissCheckingDialog() {
287        if (mCheckingDialog == null) {
288            mCheckingDialog = (CheckingDialog)
289                    getFragmentManager().findFragmentByTag(CheckingDialog.TAG);
290        }
291        if (mCheckingDialog != null) {
292            mCheckingDialog.dismiss();
293            mCheckingDialog = null;
294        }
295    }
296
297    /**
298     * This is called when the user clicks "cancel" on the progress dialog.  Shuts everything
299     * down and dismisses everything.
300     * This should cause us to remain in the current screen (not accepting the settings)
301     */
302    private void onCheckingDialogCancel() {
303        // 1. kill the checker
304        Utility.cancelTaskInterrupt(mAccountCheckTask);
305        mAccountCheckTask = null;
306        // 2. kill self with no report - this is "cancel"
307        getFragmentManager().popBackStack();
308    }
309
310    /**
311     * This is called when the user clicks "edit" from the error dialog.  The error dialog
312     * should have already dismissed itself.
313     * Depending on the context, the target will remain in the current activity (e.g. editing
314     * settings) or return to its own parent (e.g. enter new credentials).
315     */
316    private void onErrorDialogEditButton() {
317        Callbacks callbackTarget = getCallbackTarget();
318        if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) {
319            // report auth error to target fragment or activity
320            callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null);
321        } else {
322            // report check settings failure to target fragment or activity
323            callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR);
324        }
325        getFragmentManager().popBackStack();
326    }
327
328    /**
329     * This is called when the user clicks "ok" or "cancel" on the "security required" dialog.
330     * Shuts everything down and dismisses everything, and reports the result appropriately.
331     */
332    private void onSecurityRequiredDialogResultOk(boolean okPressed) {
333        // 1. handle OK/cancel - notify that security is OK and we can proceed
334        Callbacks callbackTarget = getCallbackTarget();
335        callbackTarget.onCheckSettingsComplete(
336                okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY);
337
338        // 2. kill self
339        getFragmentManager().popBackStack();
340    }
341
342    /**
343     * This exception class is used to report autodiscover results via the reporting mechanism.
344     */
345    public static class AutoDiscoverResults extends MessagingException {
346        public final HostAuth mHostAuth;
347
348        /**
349         * @param authenticationError true if auth failure, false for result (or no response)
350         * @param hostAuth null for "no autodiscover", non-null for server info to return
351         */
352        public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) {
353            super(null);
354            if (authenticationError) {
355                mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED;
356            } else {
357                mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT;
358            }
359            mHostAuth = hostAuth;
360        }
361    }
362
363    /**
364     * This AsyncTask does the actual account checking
365     *
366     * TODO: It would be better to remove the UI complete from here (the exception->string
367     * conversions).
368     */
369    private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> {
370
371        final Context mContext;
372        final int mMode;
373        final String mStoreUri;
374        final String mStoreHost;
375        final String mSenderUri;
376        final String mCheckEmail;
377        final String mCheckPassword;
378
379        /**
380         * Create task and parameterize it
381         * @param mode bits request operations
382         * @param checkAccount account holding values to be checked
383         */
384        public AccountCheckTask(int mode, Account checkAccount) {
385            mContext = getActivity().getApplicationContext();
386            mMode = mode;
387            mStoreUri = checkAccount.getStoreUri(mContext);
388            mStoreHost = checkAccount.mHostAuthRecv.mAddress;
389            mSenderUri = checkAccount.getSenderUri(mContext);
390            mCheckEmail = checkAccount.mEmailAddress;
391            mCheckPassword = checkAccount.mHostAuthRecv.mPassword;
392        }
393
394        @Override
395        protected MessagingException doInBackground(Void... params) {
396            if (DEBUG_FAKE_CHECK_CYCLE) {
397                return fakeChecker();
398            }
399
400            try {
401                if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
402                    if (isCancelled()) return null;
403                    publishProgress(STATE_CHECK_AUTODISCOVER);
404                    Log.d(Email.LOG_TAG, "Begin auto-discover for " + mCheckEmail);
405                    Store store = Store.getInstance(mStoreUri, mContext, null);
406                    Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword);
407                    // Result will be one of:
408                    //  null: remote exception - proceed to manual setup
409                    //  MessagingException.AUTHENTICATION_FAILED: username/password rejected
410                    //  Other error: proceed to manual setup
411                    //  No error: return autodiscover results
412                    if (result == null) {
413                        return new AutoDiscoverResults(false, null);
414                    }
415                    int errorCode =
416                            result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
417                    if (errorCode == MessagingException.AUTHENTICATION_FAILED) {
418                        return new AutoDiscoverResults(true, null);
419                    } else if (errorCode != MessagingException.NO_ERROR) {
420                        return new AutoDiscoverResults(false, null);
421                    } else {
422                        HostAuth serverInfo = (HostAuth)
423                            result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH);
424                        return new AutoDiscoverResults(false, serverInfo);
425                    }
426                }
427
428                // Check Incoming Settings
429                if ((mMode & SetupData.CHECK_INCOMING) != 0) {
430                    if (isCancelled()) return null;
431                    Log.d(Email.LOG_TAG, "Begin check of incoming email settings");
432                    publishProgress(STATE_CHECK_INCOMING);
433                    Store store = Store.getInstance(mStoreUri, mContext, null);
434                    Bundle bundle = store.checkSettings();
435                    int resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
436                    if (bundle != null) {
437                        resultCode = bundle.getInt(
438                                EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
439                    }
440                    if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
441                        SetupData.setPolicySet((PolicySet)bundle.getParcelable(
442                                EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
443                        return new MessagingException(
444                                MessagingException.SECURITY_POLICIES_REQUIRED, mStoreHost);
445                    }
446                    if (resultCode != MessagingException.NO_ERROR) {
447                        String errorMessage =
448                            bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
449                        return new MessagingException(resultCode, errorMessage);
450                    }
451                }
452
453                // Check Outgoing Settings
454                if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
455                    if (isCancelled()) return null;
456                    Log.d(Email.LOG_TAG, "Begin check of outgoing email settings");
457                    publishProgress(STATE_CHECK_OUTGOING);
458                    Sender sender = Sender.getInstance(mContext, mSenderUri);
459                    sender.close();
460                    sender.open();
461                    sender.close();
462                }
463
464                // If we reached the end, we completed the check(s) successfully
465                return null;
466            } catch (final MessagingException me) {
467                // Some of the legacy account checkers return errors by throwing MessagingException,
468                // which we catch and return here.
469                return me;
470            }
471        }
472
473        /**
474         * Dummy background worker, for testing UI only.
475         */
476        private MessagingException fakeChecker() {
477            // Dummy:  Publish a series of progress setups, 2 sec delays between them;
478            // then return "ok" (null)
479            final int DELAY = 2*1000;
480            if (isCancelled()) return null;
481            if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
482                publishProgress(STATE_CHECK_AUTODISCOVER);
483                try {
484                    Thread.sleep(DELAY);
485                } catch (InterruptedException e) { }
486                if (DEBUG_FAKE_CHECK_ERR) {
487                    return new MessagingException(MessagingException.AUTHENTICATION_FAILED);
488                }
489                // Return "real" AD results
490                HostAuth serverInfo = new HostAuth();
491                serverInfo.setStoreUri("eas://user:password@testserver.com");
492                return new AutoDiscoverResults(false, serverInfo);
493            }
494            if (isCancelled()) return null;
495            if ((mMode & SetupData.CHECK_INCOMING) != 0) {
496                publishProgress(STATE_CHECK_INCOMING);
497                try {
498                    Thread.sleep(DELAY);
499                } catch (InterruptedException e) { }
500                if (DEBUG_FAKE_CHECK_ERR) {
501                    return new MessagingException(MessagingException.IOERROR);
502                } else if (DEBUG_FORCE_SECURITY_REQUIRED) {
503                    return new MessagingException(
504                            MessagingException.SECURITY_POLICIES_REQUIRED);
505                }
506            }
507            if (isCancelled()) return null;
508            if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
509                publishProgress(STATE_CHECK_OUTGOING);
510                try {
511                    Thread.sleep(DELAY);
512                } catch (InterruptedException e) { }
513                if (DEBUG_FAKE_CHECK_ERR) {
514                    return new MessagingException(MessagingException.TLS_REQUIRED);
515                }
516            }
517            return null;
518        }
519
520        /**
521         * Progress reports (runs in UI thread).  This should be used for real progress only
522         * (not for errors).
523         */
524        @Override
525        protected void onProgressUpdate(Integer... progress) {
526            if (isCancelled()) return;
527            reportProgress(progress[0], 0, null, null);
528        }
529
530        /**
531         * Result handler (runs in UI thread).
532         *
533         * AutoDiscover authentication errors are handled a bit differently than the
534         * other errors;  If encountered, we display the error dialog, but we return with
535         * a different callback used only for AutoDiscover.
536         *
537         * @param result null for a successful check;  exception for various errors
538         */
539        @Override
540        protected void onPostExecute(MessagingException result) {
541            if (isCancelled()) return;
542            if (result == null) {
543                reportProgress(STATE_CHECK_OK, 0, null, null);
544            } else {
545                int progressState = STATE_CHECK_ERROR;
546                int exceptionType = result.getExceptionType();
547                String message = result.getMessage();
548                HostAuth hostAuth = null;
549                int id = 0;
550
551                switch (exceptionType) {
552                    // NOTE: AutoDiscover reports have their own reporting state, handle differently
553                    // from the other exception types
554                    case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED:
555                        id = (message == null)
556                            ? R.string.account_setup_failed_dlg_auth_message
557                            : R.string.account_setup_failed_dlg_auth_message_fmt;
558                        progressState = STATE_AUTODISCOVER_AUTH_DIALOG;
559                        break;
560                    case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT:
561                        hostAuth = ((AutoDiscoverResults)result).mHostAuth;
562                        progressState = STATE_AUTODISCOVER_RESULT;
563                        break;
564
565                    // NOTE: Security policies required has its own report state, handle it a bit
566                    // differently from the other exception types.
567                    case MessagingException.SECURITY_POLICIES_REQUIRED:
568                        progressState = STATE_CHECK_SHOW_SECURITY;
569                        break;
570
571                    // The remaining exception types are handled by setting the state to
572                    // STATE_CHECK_ERROR (above, default) and conversion to specific error strings.
573                    case MessagingException.CERTIFICATE_VALIDATION_ERROR:
574                        id = (message == null)
575                            ? R.string.account_setup_failed_dlg_certificate_message
576                            : R.string.account_setup_failed_dlg_certificate_message_fmt;
577                        break;
578                    case MessagingException.AUTHENTICATION_FAILED:
579                        id = (message == null)
580                            ? R.string.account_setup_failed_dlg_auth_message
581                            : R.string.account_setup_failed_dlg_auth_message_fmt;
582                        break;
583                    case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR:
584                        id = R.string.account_setup_failed_check_credentials_message;
585                        break;
586                    case MessagingException.IOERROR:
587                        id = R.string.account_setup_failed_ioerror;
588                        break;
589                    case MessagingException.TLS_REQUIRED:
590                        id = R.string.account_setup_failed_tls_required;
591                        break;
592                    case MessagingException.AUTH_REQUIRED:
593                        id = R.string.account_setup_failed_auth_required;
594                        break;
595                    case MessagingException.SECURITY_POLICIES_UNSUPPORTED:
596                        id = R.string.account_setup_failed_security_policies_unsupported;
597                        break;
598                    case MessagingException.PROTOCOL_VERSION_UNSUPPORTED:
599                        id = R.string.account_setup_failed_protocol_unsupported;
600                        break;
601                    case MessagingException.GENERAL_SECURITY:
602                        id = R.string.account_setup_failed_security;
603                        break;
604                    default:
605                        id = (message == null)
606                                ? R.string.account_setup_failed_dlg_server_message
607                                : R.string.account_setup_failed_dlg_server_message_fmt;
608                        break;
609                }
610                reportProgress(progressState, id, message, hostAuth);
611            }
612        }
613    }
614
615    /**
616     * Simple dialog that shows progress as we work through the settings checks.
617     * This is stateless except for its UI (e.g. current strings) and can be torn down or
618     * recreated at any time without affecting the account checking progress.
619     */
620    public static class CheckingDialog extends DialogFragment {
621        public final static String TAG = "CheckProgressDialog";
622
623        // Extras for saved instance state
624        private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress";
625
626        // UI
627        private String mProgressString;
628
629        /**
630         * Create a dialog that reports progress
631         * @param progress initial progress indication
632         */
633        public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment,
634                int progress) {
635            CheckingDialog f = new CheckingDialog();
636            f.setTargetFragment(parentFragment, progress);
637            return f;
638        }
639
640        /**
641         * Update the progress of an existing dialog
642         * @param progress latest progress to be displayed
643         */
644        public void updateProgress(int progress) {
645            mProgressString = getProgressString(progress);
646            AlertDialog dialog = (AlertDialog) getDialog();
647            dialog.setMessage(mProgressString);
648        }
649
650        @Override
651        public Dialog onCreateDialog(Bundle savedInstanceState) {
652            Context context = getActivity();
653            if (savedInstanceState != null) {
654                mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING);
655            }
656            if (mProgressString == null) {
657                mProgressString = getProgressString(getTargetRequestCode());
658            }
659            final AccountCheckSettingsFragment target =
660                (AccountCheckSettingsFragment) getTargetFragment();
661
662            ProgressDialog dialog = new ProgressDialog(context);
663            dialog.setIndeterminate(true);
664            dialog.setMessage(mProgressString);
665            dialog.setButton(DialogInterface.BUTTON_NEGATIVE,
666                    context.getString(R.string.cancel_action),
667                    new DialogInterface.OnClickListener() {
668                        public void onClick(DialogInterface dialog, int which) {
669                            dismiss();
670                            target.onCheckingDialogCancel();
671                        }
672                    });
673            return dialog;
674        }
675
676        /**
677         * Listen for cancellation, which can happen from places other than the
678         * negative button (e.g. touching outside the dialog), and stop the checker
679         */
680        @Override
681        public void onCancel(DialogInterface dialog) {
682            AccountCheckSettingsFragment target =
683                (AccountCheckSettingsFragment) getTargetFragment();
684            target.onCheckingDialogCancel();
685            super.onCancel(dialog);
686        }
687
688        @Override
689        public void onSaveInstanceState(Bundle outState) {
690            super.onSaveInstanceState(outState);
691            outState.putString(EXTRA_PROGRESS_STRING, mProgressString);
692        }
693
694        /**
695         * Convert progress to message
696         */
697        private String getProgressString(int progress) {
698            int stringId = 0;
699            switch (progress) {
700                case STATE_CHECK_AUTODISCOVER:
701                    stringId = R.string.account_setup_check_settings_retr_info_msg;
702                    break;
703                case STATE_CHECK_INCOMING:
704                    stringId = R.string.account_setup_check_settings_check_incoming_msg;
705                    break;
706                case STATE_CHECK_OUTGOING:
707                    stringId = R.string.account_setup_check_settings_check_outgoing_msg;
708                    break;
709            }
710            return getActivity().getString(stringId);
711        }
712    }
713
714    /**
715     * The standard error dialog.  Calls back to onErrorDialogButton().
716     */
717    public static class ErrorDialog extends DialogFragment {
718        public final static String TAG = "ErrorDialog";
719
720        // Bundle keys for arguments
721        private final static String ARGS_MESSAGE_ID = "ErrorDialog.Message.Id";
722        private final static String ARGS_MESSAGE_ARGS = "ErrorDialog.Message.Args";
723
724        public static ErrorDialog newInstance(AccountCheckSettingsFragment target,
725                int messageId, String... messageArguments) {
726            ErrorDialog fragment = new ErrorDialog();
727            Bundle arguments = new Bundle();
728            arguments.putInt(ARGS_MESSAGE_ID, messageId);
729            arguments.putStringArray(ARGS_MESSAGE_ARGS, messageArguments);
730            fragment.setArguments(arguments);
731            fragment.setTargetFragment(target, 0);
732            return fragment;
733        }
734
735        @Override
736        public Dialog onCreateDialog(Bundle savedInstanceState) {
737            final Context context = getActivity();
738            final Bundle arguments = getArguments();
739            final int messageId = arguments.getInt(ARGS_MESSAGE_ID);
740            final Object[] messageArguments = arguments.getStringArray(ARGS_MESSAGE_ARGS);
741            final AccountCheckSettingsFragment target =
742                    (AccountCheckSettingsFragment) getTargetFragment();
743
744            return new AlertDialog.Builder(context)
745                .setIcon(android.R.drawable.ic_dialog_alert)
746                .setTitle(context.getString(R.string.account_setup_failed_dlg_title))
747                .setMessage(context.getString(messageId, messageArguments))
748                .setCancelable(true)
749                .setPositiveButton(
750                        context.getString(R.string.account_setup_failed_dlg_edit_details_action),
751                        new DialogInterface.OnClickListener() {
752                            public void onClick(DialogInterface dialog, int which) {
753                                dismiss();
754                                target.onErrorDialogEditButton();
755                            }
756                        })
757                 .create();
758        }
759
760    }
761
762    /**
763     * The "security required" error dialog.  This is presented whenever an exchange account
764     * reports that it will require security policy control, and provide the user with the
765     * opportunity to accept or deny this.
766     *
767     * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back
768     * to the target as if the settings check was "ok".  If the user clicks "cancel", calls
769     * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the
770     * same as any other failed check.)
771     */
772    public static class SecurityRequiredDialog extends DialogFragment {
773        public final static String TAG = "SecurityRequiredDialog";
774
775        // Bundle keys for arguments
776        private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName";
777
778        public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target,
779                String hostName) {
780            SecurityRequiredDialog fragment = new SecurityRequiredDialog();
781            Bundle arguments = new Bundle();
782            arguments.putString(ARGS_HOST_NAME, hostName);
783            fragment.setArguments(arguments);
784            fragment.setTargetFragment(target, 0);
785            return fragment;
786        }
787
788        @Override
789        public Dialog onCreateDialog(Bundle savedInstanceState) {
790            final Context context = getActivity();
791            final Bundle arguments = getArguments();
792            final String hostName = arguments.getString(ARGS_HOST_NAME);
793            final AccountCheckSettingsFragment target =
794                    (AccountCheckSettingsFragment) getTargetFragment();
795
796            return new AlertDialog.Builder(context)
797                .setIcon(android.R.drawable.ic_dialog_alert)
798                .setTitle(context.getString(R.string.account_setup_security_required_title))
799                .setMessage(context.getString(
800                        R.string.account_setup_security_policies_required_fmt, hostName))
801                .setCancelable(true)
802                .setPositiveButton(
803                        context.getString(R.string.okay_action),
804                        new DialogInterface.OnClickListener() {
805                            public void onClick(DialogInterface dialog, int which) {
806                                dismiss();
807                                target.onSecurityRequiredDialogResultOk(true);
808                            }
809                        })
810                .setNegativeButton(
811                        context.getString(R.string.cancel_action),
812                        new DialogInterface.OnClickListener() {
813                            public void onClick(DialogInterface dialog, int which) {
814                                dismiss();
815                                target.onSecurityRequiredDialogResultOk(false);
816                            }
817                        })
818                 .create();
819        }
820
821    }
822
823}
824