1/*
2 * Copyright (C) 2014 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 android.accounts.AccountAuthenticatorResponse;
20import android.accounts.AccountManager;
21import android.app.ActivityManager;
22import android.app.AlertDialog;
23import android.app.Dialog;
24import android.app.DialogFragment;
25import android.app.Fragment;
26import android.app.FragmentManager;
27import android.app.FragmentTransaction;
28import android.app.LoaderManager;
29import android.app.ProgressDialog;
30import android.content.Context;
31import android.content.CursorLoader;
32import android.content.DialogInterface;
33import android.content.Intent;
34import android.content.Loader;
35import android.database.Cursor;
36import android.os.Bundle;
37import android.provider.ContactsContract;
38import android.text.TextUtils;
39import android.view.View;
40import android.view.inputmethod.InputMethodManager;
41import android.widget.Toast;
42
43import com.android.email.R;
44import com.android.email.service.EmailServiceUtils;
45import com.android.emailcommon.VendorPolicyLoader;
46import com.android.emailcommon.provider.Account;
47import com.android.emailcommon.provider.EmailContent.AccountColumns;
48import com.android.emailcommon.provider.HostAuth;
49import com.android.emailcommon.service.SyncWindow;
50import com.android.mail.utils.LogUtils;
51
52import java.net.URISyntaxException;
53import java.util.HashMap;
54import java.util.Map;
55
56public class AccountSetupFinal extends AccountSetupActivity
57        implements AccountFinalizeFragment.Callback,
58        AccountSetupNoteDialogFragment.Callback, AccountCreationFragment.Callback,
59        AccountCheckSettingsFragment.Callback, SecurityRequiredDialogFragment.Callback,
60        CheckSettingsErrorDialogFragment.Callback, CheckSettingsProgressDialogFragment.Callback,
61        AccountSetupTypeFragment.Callback, AccountSetupNamesFragment.Callback,
62        AccountSetupOptionsFragment.Callback, AccountSetupBasicsFragment.Callback,
63        AccountServerBaseFragment.Callback, AccountSetupCredentialsFragment.Callback,
64        DuplicateAccountDialogFragment.Callback, AccountSetupABFragment.Callback {
65
66    /**
67     * Direct access for forcing account creation
68     * For use by continuous automated test system (e.g. in conjunction with monkey tests)
69     *
70     * === Support for automated testing ==
71     * This activity can also be launched directly via INTENT_FORCE_CREATE_ACCOUNT. This is intended
72     * only for use by continuous test systems, and is currently only available when
73     * {@link ActivityManager#isRunningInTestHarness()} is set.  To use this mode, you must
74     * construct an intent which contains all necessary information to create the account.  No
75     * connection checking is done, so the account may or may not actually work.  Here is a sample
76     * command, for a gmail account "test_account" with a password of "test_password".
77     *
78     *      $ adb shell am start -a com.android.email.FORCE_CREATE_ACCOUNT \
79     *          -e EMAIL test_account@gmail.com \
80     *          -e USER "Test Account Name" \
81     *          -e INCOMING imap+ssl+://test_account:test_password@imap.gmail.com \
82     *          -e OUTGOING smtp+ssl+://test_account:test_password@smtp.gmail.com
83     *
84     * Note: For accounts that require the full email address in the login, encode the @ as %40.
85     * Note: Exchange accounts that require device security policies cannot be created
86     * automatically.
87     *
88     * For accounts that correspond to services in providers.xml you can also use the following form
89     *
90     *      $adb shell am start -a com.android.email.FORCE_CREATE_ACCOUNT \
91     *          -e EMAIL test_account@gmail.com \
92     *          -e PASSWORD test_password
93     *
94     * and the appropriate incoming/outgoing information will be filled in automatically.
95     */
96    private static String INTENT_FORCE_CREATE_ACCOUNT;
97    private static final String EXTRA_FLOW_MODE = "FLOW_MODE";
98    private static final String EXTRA_FLOW_ACCOUNT_TYPE = "FLOW_ACCOUNT_TYPE";
99    private static final String EXTRA_CREATE_ACCOUNT_EMAIL = "EMAIL";
100    private static final String EXTRA_CREATE_ACCOUNT_USER = "USER";
101    private static final String EXTRA_CREATE_ACCOUNT_PASSWORD = "PASSWORD";
102    private static final String EXTRA_CREATE_ACCOUNT_INCOMING = "INCOMING";
103    private static final String EXTRA_CREATE_ACCOUNT_OUTGOING = "OUTGOING";
104    private static final String EXTRA_CREATE_ACCOUNT_SYNC_LOOKBACK = "SYNC_LOOKBACK";
105
106    private static final String CREATE_ACCOUNT_SYNC_ALL_VALUE = "ALL";
107
108    private static final Boolean DEBUG_ALLOW_NON_TEST_HARNESS_CREATION = false;
109
110    protected static final String ACTION_JUMP_TO_INCOMING = "jumpToIncoming";
111    protected static final String ACTION_JUMP_TO_OUTGOING = "jumpToOutgoing";
112    protected static final String ACTION_JUMP_TO_OPTIONS = "jumpToOptions";
113
114    private static final String SAVESTATE_KEY_IS_PROCESSING = "AccountSetupFinal.is_processing";
115    private static final String SAVESTATE_KEY_STATE = "AccountSetupFinal.state";
116    private static final String SAVESTATE_KEY_PROVIDER = "AccountSetupFinal.provider";
117    private static final String SAVESTATE_KEY_AUTHENTICATOR_RESPONSE = "AccountSetupFinal.authResp";
118    private static final String SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR =
119            "AccountSetupFinal.authErr";
120    private static final String SAVESTATE_KEY_IS_PRE_CONFIGURED = "AccountSetupFinal.preconfig";
121    private static final String SAVESTATE_KEY_SKIP_AUTO_DISCOVER = "AccountSetupFinal.noAuto";
122    private static final String SAVESTATE_KEY_PASSWORD_FAILED = "AccountSetupFinal.passwordFailed";
123
124    private static final String CONTENT_FRAGMENT_TAG = "AccountSetupContentFragment";
125    private static final String CREDENTIALS_BACKSTACK_TAG = "AccountSetupCredentialsFragment";
126
127    // Collecting initial email and password
128    private static final int STATE_BASICS = 0;
129    // Show the user some interstitial message after email entry
130    private static final int STATE_BASICS_POST = 1;
131    // Account is not pre-configured, query user for account type
132    private static final int STATE_TYPE = 2;
133    // Account is pre-configured, but the user picked a different protocol
134    private static final int STATE_AB = 3;
135    // Collect initial password or oauth token
136    private static final int STATE_CREDENTIALS = 4;
137    // Account is a pre-configured account, run the checker
138    private static final int STATE_CHECKING_PRECONFIGURED = 5;
139    // Auto-discovering exchange account info, possibly other protocols later
140    private static final int STATE_AUTO_DISCOVER = 6;
141    // User is entering incoming settings
142    private static final int STATE_MANUAL_INCOMING = 7;
143    // We're checking incoming settings
144    private static final int STATE_CHECKING_INCOMING = 8;
145    // User is entering outgoing settings
146    private static final int STATE_MANUAL_OUTGOING = 9;
147    // We're checking outgoing settings
148    private static final int STATE_CHECKING_OUTGOING = 10;
149    // User is entering sync options
150    private static final int STATE_OPTIONS = 11;
151    // We're creating the account
152    private static final int STATE_CREATING = 12;
153    // User is entering account name and real name
154    private static final int STATE_NAMES = 13;
155    // we're finalizing the account
156    private static final int STATE_FINALIZE = 14;
157
158    private int mState = STATE_BASICS;
159
160    private boolean mIsProcessing = false;
161    private boolean mForceCreate = false;
162    private boolean mReportAccountAuthenticatorError;
163    private AccountAuthenticatorResponse mAccountAuthenticatorResponse;
164    // True if this provider is found in our providers.xml, set after Basics
165    private boolean mIsPreConfiguredProvider = false;
166    // True if the user selected manual setup
167    private boolean mSkipAutoDiscover = false;
168    // True if validating the pre-configured provider failed and we want manual setup
169    private boolean mPreConfiguredFailed = false;
170
171    private VendorPolicyLoader.Provider mProvider;
172    private boolean mPasswordFailed;
173
174    private static final int OWNER_NAME_LOADER_ID = 0;
175    private String mOwnerName;
176
177    private static final int EXISTING_ACCOUNTS_LOADER_ID = 1;
178    private Map<String, String> mExistingAccountsMap;
179
180    public static Intent actionNewAccountIntent(final Context context) {
181        final Intent i = new Intent(context, AccountSetupFinal.class);
182        i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NORMAL);
183        return i;
184    }
185
186    public static Intent actionNewAccountWithResultIntent(final Context context) {
187        final Intent i = new Intent(context, AccountSetupFinal.class);
188        i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_NO_ACCOUNTS);
189        return i;
190    }
191
192    public static Intent actionGetCreateAccountIntent(final Context context,
193            final String accountManagerType) {
194        final Intent i = new Intent(context, AccountSetupFinal.class);
195        i.putExtra(EXTRA_FLOW_MODE, SetupDataFragment.FLOW_MODE_ACCOUNT_MANAGER);
196        i.putExtra(EXTRA_FLOW_ACCOUNT_TYPE, accountManagerType);
197        return i;
198    }
199
200    @Override
201    public void onCreate(Bundle savedInstanceState) {
202        super.onCreate(savedInstanceState);
203
204        final Intent intent = getIntent();
205        final String action = intent.getAction();
206
207        if (INTENT_FORCE_CREATE_ACCOUNT == null) {
208            INTENT_FORCE_CREATE_ACCOUNT = getString(R.string.intent_force_create_email_account);
209        }
210
211        setContentView(R.layout.account_setup_activity);
212
213        if (savedInstanceState != null) {
214            mIsProcessing = savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PROCESSING, false);
215            mState = savedInstanceState.getInt(SAVESTATE_KEY_STATE, STATE_OPTIONS);
216            mProvider = (VendorPolicyLoader.Provider)
217                    savedInstanceState.getSerializable(SAVESTATE_KEY_PROVIDER);
218            mAccountAuthenticatorResponse =
219                    savedInstanceState.getParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE);
220            mReportAccountAuthenticatorError =
221                    savedInstanceState.getBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR);
222            mIsPreConfiguredProvider =
223                    savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED);
224            mSkipAutoDiscover = savedInstanceState.getBoolean(SAVESTATE_KEY_SKIP_AUTO_DISCOVER);
225            mPasswordFailed = savedInstanceState.getBoolean(SAVESTATE_KEY_PASSWORD_FAILED);
226        } else {
227            // If we're not restoring from a previous state, we want to configure the initial screen
228
229            // Set aside incoming AccountAuthenticatorResponse, if there was any
230            mAccountAuthenticatorResponse = getIntent()
231                    .getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
232            if (mAccountAuthenticatorResponse != null) {
233                // When this Activity is called as part of account authentification flow,
234                // we are responsible for eventually reporting the result (success or failure) to
235                // the account manager.  Most exit paths represent an failed or abandoned setup,
236                // so the default is to report the error.  Success will be reported by the code in
237                // AccountSetupOptions that commits the finally created account.
238                mReportAccountAuthenticatorError = true;
239            }
240
241            // Initialize the SetupDataFragment
242            if (INTENT_FORCE_CREATE_ACCOUNT.equals(action)) {
243                mSetupData.setFlowMode(SetupDataFragment.FLOW_MODE_FORCE_CREATE);
244            } else {
245                final int intentFlowMode = intent.getIntExtra(EXTRA_FLOW_MODE,
246                        SetupDataFragment.FLOW_MODE_UNSPECIFIED);
247                final String flowAccountType = intent.getStringExtra(EXTRA_FLOW_ACCOUNT_TYPE);
248                mSetupData.setAmProtocol(
249                        EmailServiceUtils.getProtocolFromAccountType(this, flowAccountType));
250                mSetupData.setFlowMode(intentFlowMode);
251            }
252
253            mState = STATE_BASICS;
254            // Support unit testing individual screens
255            if (TextUtils.equals(ACTION_JUMP_TO_INCOMING, action)) {
256                mState = STATE_MANUAL_INCOMING;
257            } else if (TextUtils.equals(ACTION_JUMP_TO_OUTGOING, action)) {
258                mState = STATE_MANUAL_OUTGOING;
259            } else if (TextUtils.equals(ACTION_JUMP_TO_OPTIONS, action)) {
260                mState = STATE_OPTIONS;
261            }
262            updateContentFragment(false /* addToBackstack */);
263            mPasswordFailed = false;
264        }
265
266        if (!mIsProcessing
267                && mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_FORCE_CREATE) {
268            /**
269             * To support continuous testing, we allow the forced creation of accounts.
270             * This works in a manner fairly similar to automatic setup, in which the complete
271             * server Uri's are available, except that we will also skip checking (as if both
272             * checks were true) and all other UI.
273             *
274             * email: The email address for the new account
275             * user: The user name for the new account
276             * incoming: The URI-style string defining the incoming account
277             * outgoing: The URI-style string defining the outgoing account
278             */
279            final String email = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_EMAIL);
280            final String user = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_USER);
281            final String password = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_PASSWORD);
282            final String incoming = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_INCOMING);
283            final String outgoing = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_OUTGOING);
284            final String syncLookbackText = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_SYNC_LOOKBACK);
285            final int syncLookback;
286            if (TextUtils.equals(syncLookbackText, CREATE_ACCOUNT_SYNC_ALL_VALUE)) {
287                syncLookback = SyncWindow.SYNC_WINDOW_ALL;
288            } else {
289                syncLookback = -1;
290            }
291            // If we've been explicitly provided with all the details to fill in the account, we
292            // can use them
293            final boolean explicitForm = !(TextUtils.isEmpty(user) ||
294                    TextUtils.isEmpty(incoming) || TextUtils.isEmpty(outgoing));
295            // If we haven't been provided the details, but we have the password, we can look up
296            // the info from providers.xml
297            final boolean implicitForm = !TextUtils.isEmpty(password) && !explicitForm;
298            if (TextUtils.isEmpty(email) || !(explicitForm || implicitForm)) {
299                LogUtils.e(LogUtils.TAG, "Force account create requires extras EMAIL, " +
300                        "USER, INCOMING, OUTGOING, or EMAIL and PASSWORD");
301                finish();
302                return;
303            }
304
305            if (implicitForm) {
306                final String[] emailParts = email.split("@");
307                final String domain = emailParts[1].trim();
308                mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
309                if (mProvider == null) {
310                    LogUtils.e(LogUtils.TAG, "findProviderForDomain couldn't find provider");
311                    finish();
312                    return;
313                }
314                mIsPreConfiguredProvider = true;
315                mSetupData.setEmail(email);
316                boolean autoSetupCompleted = finishAutoSetup();
317                if (!autoSetupCompleted) {
318                    LogUtils.e(LogUtils.TAG, "Force create account failed to create account");
319                    finish();
320                    return;
321                }
322                final Account account = mSetupData.getAccount();
323                final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
324                recvAuth.mPassword = password;
325                final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
326                sendAuth.mPassword = password;
327            } else {
328                final Account account = mSetupData.getAccount();
329
330                try {
331                    final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
332                    recvAuth.setHostAuthFromString(incoming);
333
334                    final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
335                    sendAuth.setHostAuthFromString(outgoing);
336                } catch (URISyntaxException e) {
337                    // If we can't set up the URL, don't continue
338                    Toast.makeText(this, R.string.account_setup_username_password_toast,
339                            Toast.LENGTH_LONG)
340                            .show();
341                    finish();
342                    return;
343                }
344
345                populateSetupData(user, email);
346                // We need to do this after calling populateSetupData(), because that will
347                // overwrite it with the default values.
348                if (syncLookback >= SyncWindow.SYNC_WINDOW_ACCOUNT &&
349                    syncLookback <= SyncWindow.SYNC_WINDOW_ALL) {
350                    account.mSyncLookback = syncLookback;
351                }
352            }
353
354            mState = STATE_OPTIONS;
355            updateContentFragment(false /* addToBackstack */);
356            getFragmentManager().executePendingTransactions();
357
358            if (!DEBUG_ALLOW_NON_TEST_HARNESS_CREATION &&
359                    !ActivityManager.isRunningInTestHarness()) {
360                LogUtils.e(LogUtils.TAG,
361                        "ERROR: Force account create only allowed while in test harness");
362                finish();
363                return;
364            }
365
366            mForceCreate = true;
367        }
368
369        // Launch a loader to look up the owner name.  It should be ready well in advance of
370        // the time the user clicks next or manual.
371        getLoaderManager().initLoader(OWNER_NAME_LOADER_ID, null,
372                new LoaderManager.LoaderCallbacks<Cursor>() {
373                    @Override
374                    public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
375                        return new CursorLoader(AccountSetupFinal.this,
376                                ContactsContract.Profile.CONTENT_URI,
377                                new String[] {ContactsContract.Profile.DISPLAY_NAME_PRIMARY},
378                                null, null, null);
379                    }
380
381                    @Override
382                    public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
383                        if (data != null && data.moveToFirst()) {
384                            mOwnerName = data.getString(data.getColumnIndex(
385                                    ContactsContract.Profile.DISPLAY_NAME_PRIMARY));
386                        }
387                    }
388
389                    @Override
390                    public void onLoaderReset(final Loader<Cursor> loader) {}
391                });
392
393        // Launch a loader to cache some info about existing accounts so we can dupe-check against
394        // them.
395        getLoaderManager().initLoader(EXISTING_ACCOUNTS_LOADER_ID, null,
396                new LoaderManager.LoaderCallbacks<Cursor> () {
397                    @Override
398                    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
399                        return new CursorLoader(AccountSetupFinal.this, Account.CONTENT_URI,
400                                new String[] {AccountColumns.EMAIL_ADDRESS,
401                                        AccountColumns.DISPLAY_NAME},
402                                null, null, null);
403                    }
404
405                    @Override
406                    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
407                        if (data == null) {
408                            mExistingAccountsMap = null;
409                            return;
410                        }
411
412                        mExistingAccountsMap = new HashMap<String, String>();
413
414                        final int emailColumnIndex = data.getColumnIndex(
415                                AccountColumns.EMAIL_ADDRESS);
416                        final int displayNameColumnIndex =
417                                data.getColumnIndex(AccountColumns.DISPLAY_NAME);
418
419                        while (data.moveToNext()) {
420                            final String email = data.getString(emailColumnIndex);
421                            final String displayName = data.getString(displayNameColumnIndex);
422                            mExistingAccountsMap.put(email,
423                                    TextUtils.isEmpty(displayName) ? email : displayName);
424                        }
425                    }
426
427                    @Override
428                    public void onLoaderReset(Loader<Cursor> loader) {
429                        mExistingAccountsMap = null;
430                    }
431                });
432    }
433
434    @Override
435    protected void onResume() {
436        super.onResume();
437        if (mForceCreate) {
438            mForceCreate = false;
439
440            // We need to do this after onCreate so that we can ensure that the fragment is
441            // fully created before querying it.
442            // This will call initiateAccountCreation() for us
443            proceed();
444        }
445    }
446
447    @Override
448    public void onSaveInstanceState(Bundle outState) {
449        super.onSaveInstanceState(outState);
450        outState.putBoolean(SAVESTATE_KEY_IS_PROCESSING, mIsProcessing);
451        outState.putInt(SAVESTATE_KEY_STATE, mState);
452        outState.putSerializable(SAVESTATE_KEY_PROVIDER, mProvider);
453        outState.putParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE, mAccountAuthenticatorResponse);
454        outState.putBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR,
455                mReportAccountAuthenticatorError);
456        outState.putBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED, mIsPreConfiguredProvider);
457        outState.putBoolean(SAVESTATE_KEY_PASSWORD_FAILED, mPasswordFailed);
458    }
459
460    /**
461     * Swap in the new fragment according to mState. This pushes the current fragment onto the back
462     * stack, so only call it once per transition.
463     */
464    private void updateContentFragment(boolean addToBackstack) {
465        final AccountSetupFragment f;
466        String backstackTag = null;
467
468        switch (mState) {
469            case STATE_BASICS:
470                f = AccountSetupBasicsFragment.newInstance();
471                break;
472            case STATE_TYPE:
473                f = AccountSetupTypeFragment.newInstance();
474                break;
475            case STATE_AB:
476                f = AccountSetupABFragment.newInstance(mSetupData.getEmail(),
477                        mSetupData.getAmProtocol(), mSetupData.getIncomingProtocol(this));
478                break;
479            case STATE_CREDENTIALS:
480                f = AccountSetupCredentialsFragment.newInstance(mSetupData.getEmail(),
481                        mSetupData.getIncomingProtocol(this), mSetupData.getClientCert(this),
482                        mPasswordFailed, false /* standalone */);
483                backstackTag = CREDENTIALS_BACKSTACK_TAG;
484                break;
485            case STATE_MANUAL_INCOMING:
486                f = AccountSetupIncomingFragment.newInstance(false);
487                break;
488            case STATE_MANUAL_OUTGOING:
489                f = AccountSetupOutgoingFragment.newInstance(false);
490                break;
491            case STATE_OPTIONS:
492                f = AccountSetupOptionsFragment.newInstance();
493                break;
494            case STATE_NAMES:
495                f = AccountSetupNamesFragment.newInstance();
496                break;
497            default:
498                throw new IllegalStateException("Incorrect state " + mState);
499        }
500        f.setState(mState);
501        final FragmentTransaction ft = getFragmentManager().beginTransaction();
502        ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
503        ft.replace(R.id.setup_fragment_container, f, CONTENT_FRAGMENT_TAG);
504        if (addToBackstack) {
505            ft.addToBackStack(backstackTag);
506        }
507        ft.commit();
508
509        final InputMethodManager imm =
510                (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
511        final View fragment_container = findViewById(R.id.setup_fragment_container);
512        imm.hideSoftInputFromWindow(fragment_container.getWindowToken(),
513                0 /* flags: always hide */);
514    }
515
516    /**
517     * Retrieve the current content fragment
518     * @return The content fragment or null if it wasn't found for some reason
519     */
520    private AccountSetupFragment getContentFragment() {
521        return (AccountSetupFragment) getFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
522    }
523
524    /**
525     * Reads the flow state saved into the current fragment and restores mState to it, also
526     * resetting the headline at the same time.
527     */
528    private void resetStateFromCurrentFragment() {
529        AccountSetupFragment f = getContentFragment();
530        mState = f.getState();
531    }
532
533    /**
534     * Main choreography function to handle moving forward through scenes. Moving back should be
535     * generally handled for us by the back stack
536     */
537    protected void proceed() {
538        mIsProcessing = false;
539        final AccountSetupFragment oldContentFragment = getContentFragment();
540        if (oldContentFragment != null) {
541            oldContentFragment.setNextButtonEnabled(true);
542        }
543
544        getFragmentManager().executePendingTransactions();
545
546        switch (mState) {
547            case STATE_BASICS:
548                final boolean advance = onBasicsComplete();
549                if (!advance) {
550                    mState = STATE_BASICS_POST;
551                    break;
552                } // else fall through
553            case STATE_BASICS_POST:
554                if (shouldDivertToManual()) {
555                    mSkipAutoDiscover = true;
556                    mIsPreConfiguredProvider = false;
557                    mState = STATE_TYPE;
558                } else {
559                    mSkipAutoDiscover = false;
560                    if (mIsPreConfiguredProvider) {
561                        if (!TextUtils.isEmpty(mSetupData.getAmProtocol()) &&
562                                !TextUtils.equals(mSetupData.getAmProtocol(),
563                                        mSetupData.getIncomingProtocol(this))) {
564                            mState = STATE_AB;
565                        } else {
566                            mState = STATE_CREDENTIALS;
567                            if (possiblyDivertToGmail()) {
568                                return;
569                            }
570                        }
571                    } else {
572                        final String amProtocol = mSetupData.getAmProtocol();
573                        if (!TextUtils.isEmpty(amProtocol)) {
574                            mSetupData.setIncomingProtocol(this, amProtocol);
575                            final Account account = mSetupData.getAccount();
576                            setDefaultsForProtocol(account);
577                            mState = STATE_CREDENTIALS;
578                        } else {
579                            mState = STATE_TYPE;
580                        }
581                    }
582                }
583                updateContentFragment(true /* addToBackstack */);
584                break;
585            case STATE_TYPE:
586                // We either got here through "Manual Setup" or because we didn't find the provider
587                mState = STATE_CREDENTIALS;
588                updateContentFragment(true /* addToBackstack */);
589                break;
590            case STATE_AB:
591                if (possiblyDivertToGmail()) {
592                    return;
593                }
594                mState = STATE_CREDENTIALS;
595                updateContentFragment(true /* addToBackstack */);
596                break;
597            case STATE_CREDENTIALS:
598                collectCredentials();
599                if (mIsPreConfiguredProvider) {
600                    mState = STATE_CHECKING_PRECONFIGURED;
601                    initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING
602                            | SetupDataFragment.CHECK_OUTGOING);
603                } else {
604                    populateHostAuthsFromSetupData();
605                    if (mSkipAutoDiscover) {
606                        mState = STATE_MANUAL_INCOMING;
607                        updateContentFragment(true /* addToBackstack */);
608                    } else {
609                        mState = STATE_AUTO_DISCOVER;
610                        initiateAutoDiscover();
611                    }
612                }
613                break;
614            case STATE_CHECKING_PRECONFIGURED:
615                if (mPreConfiguredFailed) {
616                    if (mPasswordFailed) {
617                        // Get rid of the previous instance of the AccountSetupCredentialsFragment.
618                        FragmentManager fm = getFragmentManager();
619                        fm.popBackStackImmediate(CREDENTIALS_BACKSTACK_TAG, 0);
620                        final AccountSetupCredentialsFragment f = (AccountSetupCredentialsFragment)
621                                getContentFragment();
622                        f.setPasswordFailed(mPasswordFailed);
623                        resetStateFromCurrentFragment();
624                    } else {
625                        mState = STATE_MANUAL_INCOMING;
626                        updateContentFragment(true /* addToBackstack */);
627                    }
628                } else {
629                    mState = STATE_OPTIONS;
630                    updateContentFragment(true /* addToBackstack */);
631                }
632                break;
633            case STATE_AUTO_DISCOVER:
634                // TODO: figure out if we can skip past manual setup
635                mState = STATE_MANUAL_INCOMING;
636                updateContentFragment(true);
637                break;
638            case STATE_MANUAL_INCOMING:
639                onIncomingComplete();
640                mState = STATE_CHECKING_INCOMING;
641                initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING);
642                break;
643            case STATE_CHECKING_INCOMING:
644                final EmailServiceUtils.EmailServiceInfo serviceInfo =
645                        mSetupData.getIncomingServiceInfo(this);
646                if (serviceInfo.usesSmtp) {
647                    mState = STATE_MANUAL_OUTGOING;
648                } else {
649                    mState = STATE_OPTIONS;
650                }
651                updateContentFragment(true /* addToBackstack */);
652                break;
653            case STATE_MANUAL_OUTGOING:
654                onOutgoingComplete();
655                mState = STATE_CHECKING_OUTGOING;
656                initiateCheckSettingsFragment(SetupDataFragment.CHECK_OUTGOING);
657                break;
658            case STATE_CHECKING_OUTGOING:
659                mState = STATE_OPTIONS;
660                updateContentFragment(true /* addToBackstack */);
661                break;
662            case STATE_OPTIONS:
663                mState = STATE_CREATING;
664                initiateAccountCreation();
665                break;
666            case STATE_CREATING:
667                mState = STATE_NAMES;
668                updateContentFragment(true /* addToBackstack */);
669                if (mSetupData.getFlowMode() == SetupDataFragment.FLOW_MODE_FORCE_CREATE) {
670                    getFragmentManager().executePendingTransactions();
671                    initiateAccountFinalize();
672                }
673                break;
674            case STATE_NAMES:
675                initiateAccountFinalize();
676                break;
677            case STATE_FINALIZE:
678                finish();
679                break;
680            default:
681                LogUtils.wtf(LogUtils.TAG, "Unknown state %d", mState);
682                break;
683        }
684    }
685
686    /**
687     * Check if we should divert to creating a Gmail account instead
688     * @return true if we diverted
689     */
690    private boolean possiblyDivertToGmail() {
691        // TODO: actually divert here
692        final EmailServiceUtils.EmailServiceInfo info =
693                mSetupData.getIncomingServiceInfo(this);
694        if (TextUtils.equals(info.protocol, "gmail")) {
695            final Bundle options = new Bundle(1);
696            options.putBoolean("allowSkip", false);
697            AccountManager.get(this).addAccount("com.google",
698                    "mail" /* authTokenType */,
699                    null,
700                    options,
701                    this, null, null);
702
703            finish();
704            return true;
705        }
706        return false;
707    }
708
709    /**
710     * Block the back key if we are currently processing the "next" key"
711     */
712    @Override
713    public void onBackPressed() {
714        if (mIsProcessing) {
715            return;
716        }
717        if (mState == STATE_NAMES) {
718            finish();
719        } else {
720            super.onBackPressed();
721        }
722        // After super.onBackPressed() our fragment should be in place, so query the state we
723        // installed it for
724        resetStateFromCurrentFragment();
725    }
726
727    @Override
728    public void setAccount(Account account) {
729        mSetupData.setAccount(account);
730    }
731
732    @Override
733    public void finish() {
734        // If the account manager initiated the creation, and success was not reported,
735        // then we assume that we're giving up (for any reason) - report failure.
736        if (mReportAccountAuthenticatorError) {
737            if (mAccountAuthenticatorResponse != null) {
738                mAccountAuthenticatorResponse
739                        .onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
740                mAccountAuthenticatorResponse = null;
741            }
742        }
743        super.finish();
744    }
745
746    @Override
747    public void onNextButton() {
748        // Some states are handled without UI, block double-presses here
749        if (!mIsProcessing) {
750            proceed();
751        }
752    }
753
754    /**
755     * @return true to proceed, false to remain on the current screen
756     */
757    private boolean onBasicsComplete() {
758        final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment();
759        final String email = f.getEmail();
760
761        // Reset the protocol choice in case the user has back-navigated here
762        mSetupData.setIncomingProtocol(this, null);
763
764        if (!TextUtils.equals(email, mSetupData.getEmail())) {
765            // If the user changes their email address, clear the password failed state
766            mPasswordFailed = false;
767        }
768        mSetupData.setEmail(email);
769
770        final String[] emailParts = email.split("@");
771        final String domain = emailParts[1].trim();
772        mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
773        if (mProvider != null) {
774            mIsPreConfiguredProvider = true;
775            if (mProvider.note != null) {
776                final AccountSetupNoteDialogFragment dialogFragment =
777                        AccountSetupNoteDialogFragment.newInstance(mProvider.note);
778                dialogFragment.show(getFragmentManager(), AccountSetupNoteDialogFragment.TAG);
779                return false;
780            } else {
781                return finishAutoSetup();
782            }
783        } else {
784            mIsPreConfiguredProvider = false;
785            final String existingAccountName =
786                mExistingAccountsMap != null ? mExistingAccountsMap.get(email) : null;
787            if (!TextUtils.isEmpty(existingAccountName)) {
788                showDuplicateAccountDialog(existingAccountName);
789                return false;
790            } else {
791                populateSetupData(mOwnerName, email);
792                mSkipAutoDiscover = false;
793                return true;
794            }
795        }
796    }
797
798    private void showDuplicateAccountDialog(final String existingAccountName) {
799        final DuplicateAccountDialogFragment dialogFragment =
800                DuplicateAccountDialogFragment.newInstance(existingAccountName);
801        dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG);
802    }
803
804    @Override
805    public void onDuplicateAccountDialogDismiss() {
806        resetStateFromCurrentFragment();
807    }
808
809    private boolean shouldDivertToManual() {
810        final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment();
811        return f.isManualSetup();
812    }
813
814    @Override
815    public void onCredentialsComplete(Bundle results) {
816        proceed();
817    }
818
819    private void collectCredentials() {
820        final AccountSetupCredentialsFragment f = (AccountSetupCredentialsFragment)
821                getContentFragment();
822        final Bundle results = f.getCredentialResults();
823        mSetupData.setCredentialResults(results);
824        final Account account = mSetupData.getAccount();
825        final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
826        AccountSetupCredentialsFragment.populateHostAuthWithResults(this, recvAuth,
827                mSetupData.getCredentialResults());
828        mSetupData.setIncomingCredLoaded(true);
829        final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
830        if (info.usesSmtp) {
831            final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
832            AccountSetupCredentialsFragment.populateHostAuthWithResults(this, sendAuth,
833                    mSetupData.getCredentialResults());
834            mSetupData.setOutgoingCredLoaded(true);
835        }
836    }
837
838    @Override
839    public void onNoteDialogComplete() {
840        finishAutoSetup();
841        proceed();
842    }
843
844    @Override
845    public void onNoteDialogCancel() {
846        resetStateFromCurrentFragment();
847    }
848
849    /**
850     * Finish the auto setup process, in some cases after showing a warning dialog.
851     * Happens after onBasicsComplete
852     * @return true to proceed, false to remain on the current screen
853     */
854    private boolean finishAutoSetup() {
855        final String email = mSetupData.getEmail();
856
857        try {
858            mProvider.expandTemplates(email);
859
860            final String primaryProtocol = HostAuth.getProtocolFromString(mProvider.incomingUri);
861            EmailServiceUtils.EmailServiceInfo info =
862                    EmailServiceUtils.getServiceInfo(this, primaryProtocol);
863            // If the protocol isn't one we can use, and we're not diverting to gmail, try the alt
864            if (!info.isGmailStub && !EmailServiceUtils.isServiceAvailable(this, info.protocol)) {
865                LogUtils.d(LogUtils.TAG, "Protocol %s not available, using alternate",
866                        info.protocol);
867                mProvider.expandAlternateTemplates(email);
868                final String alternateProtocol = HostAuth.getProtocolFromString(
869                        mProvider.incomingUri);
870                info = EmailServiceUtils.getServiceInfo(this, alternateProtocol);
871            }
872            final Account account = mSetupData.getAccount();
873            final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
874            recvAuth.setHostAuthFromString(mProvider.incomingUri);
875
876            recvAuth.setUserName(mProvider.incomingUsername);
877            recvAuth.mPort =
878                    ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) ? info.portSsl : info.port;
879
880            if (info.usesSmtp) {
881                final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
882                sendAuth.setHostAuthFromString(mProvider.outgoingUri);
883                sendAuth.setUserName(mProvider.outgoingUsername);
884            }
885
886            // Populate the setup data, assuming that the duplicate account check will succeed
887            populateSetupData(mOwnerName, email);
888
889            final String duplicateAccountName =
890                    mExistingAccountsMap != null ? mExistingAccountsMap.get(email) : null;
891            if (duplicateAccountName != null) {
892                showDuplicateAccountDialog(duplicateAccountName);
893                return false;
894            }
895        } catch (URISyntaxException e) {
896            mSkipAutoDiscover = false;
897            mPreConfiguredFailed = true;
898        }
899        return true;
900    }
901
902
903    /**
904     * Helper method to fill in some per-protocol defaults
905     * @param account Account object to fill in
906     */
907    public void setDefaultsForProtocol(Account account) {
908        final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
909        if (info == null) return;
910        account.mSyncInterval = info.defaultSyncInterval;
911        account.mSyncLookback = info.defaultLookback;
912        if (info.offerLocalDeletes) {
913            account.setDeletePolicy(info.defaultLocalDeletes);
914        }
915    }
916
917    /**
918     * Populate SetupData's account with complete setup info, assumes that the receive auth is
919     * created and its protocol is set
920     */
921    private void populateSetupData(String senderName, String senderEmail) {
922        final Account account = mSetupData.getAccount();
923        account.setSenderName(senderName);
924        account.setEmailAddress(senderEmail);
925        account.setDisplayName(senderEmail);
926        setDefaultsForProtocol(account);
927    }
928
929    private void onIncomingComplete() {
930        AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment();
931        f.collectUserInput();
932    }
933
934    private void onOutgoingComplete() {
935        AccountSetupOutgoingFragment f = (AccountSetupOutgoingFragment) getContentFragment();
936        f.collectUserInput();
937    }
938
939    // This callback method is only applicable to using Incoming/Outgoing fragments in settings mode
940    @Override
941    public void onAccountServerUIComplete(int checkMode) {}
942
943    // This callback method is only applicable to using Incoming/Outgoing fragments in settings mode
944    @Override
945    public void onAccountServerSaveComplete() {}
946
947    private void populateHostAuthsFromSetupData() {
948        final String email = mSetupData.getEmail();
949        final String[] emailParts = email.split("@");
950        final String domain = emailParts[1];
951
952        final Account account = mSetupData.getAccount();
953
954        final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
955        recvAuth.setUserName(email);
956        recvAuth.setConnection(mSetupData.getIncomingProtocol(), domain,
957                HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE);
958        AccountSetupCredentialsFragment.populateHostAuthWithResults(this, recvAuth,
959                mSetupData.getCredentialResults());
960        mSetupData.setIncomingCredLoaded(true);
961
962        final EmailServiceUtils.EmailServiceInfo info =
963                mSetupData.getIncomingServiceInfo(this);
964        if (info.usesSmtp) {
965            final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
966            sendAuth.setUserName(email);
967            sendAuth.setConnection(HostAuth.LEGACY_SCHEME_SMTP, domain,
968                    HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE);
969            AccountSetupCredentialsFragment.populateHostAuthWithResults(this, sendAuth,
970                    mSetupData.getCredentialResults());
971            mSetupData.setOutgoingCredLoaded(true);
972        }
973    }
974
975    private void initiateAutoDiscover() {
976        // Populate the setup data, assuming that the duplicate account check will succeed
977        initiateCheckSettingsFragment(SetupDataFragment.CHECK_AUTODISCOVER);
978    }
979
980    private void initiateCheckSettingsFragment(int checkMode) {
981        final Fragment f = AccountCheckSettingsFragment.newInstance(checkMode);
982        final Fragment d = CheckSettingsProgressDialogFragment.newInstance(checkMode);
983        getFragmentManager().beginTransaction()
984                .add(f, AccountCheckSettingsFragment.TAG)
985                .add(d, CheckSettingsProgressDialogFragment.TAG)
986                .commit();
987    }
988
989    @Override
990    public void onCheckSettingsProgressDialogCancel() {
991        dismissCheckSettingsFragment();
992        resetStateFromCurrentFragment();
993    }
994
995    private void dismissCheckSettingsFragment() {
996        final Fragment f = getFragmentManager().findFragmentByTag(AccountCheckSettingsFragment.TAG);
997        final Fragment d =
998                getFragmentManager().findFragmentByTag(CheckSettingsProgressDialogFragment.TAG);
999        getFragmentManager().beginTransaction()
1000                .remove(f)
1001                .remove(d)
1002                .commit();
1003    }
1004
1005    @Override
1006    public void onCheckSettingsError(int reason, String message) {
1007        if (reason == CheckSettingsErrorDialogFragment.REASON_AUTHENTICATION_FAILED ||
1008                reason == CheckSettingsErrorDialogFragment.REASON_CERTIFICATE_REQUIRED) {
1009            // TODO: possibly split password and cert error conditions
1010            mPasswordFailed = true;
1011        }
1012        dismissCheckSettingsFragment();
1013        final DialogFragment f =
1014                CheckSettingsErrorDialogFragment.newInstance(reason, message);
1015        f.show(getFragmentManager(), CheckSettingsErrorDialogFragment.TAG);
1016    }
1017
1018    @Override
1019    public void onCheckSettingsErrorDialogEditCertificate() {
1020        if (mState == STATE_CHECKING_PRECONFIGURED) {
1021            mPreConfiguredFailed = true;
1022            proceed();
1023        } else {
1024            resetStateFromCurrentFragment();
1025        }
1026        final AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment();
1027        f.onCertificateRequested();
1028    }
1029
1030    @Override
1031    public void onCheckSettingsErrorDialogEditSettings() {
1032        // If we're checking pre-configured, set a flag that we failed and navigate forwards to
1033        // incoming settings
1034        if (mState == STATE_CHECKING_PRECONFIGURED || mState == STATE_AUTO_DISCOVER) {
1035            mPreConfiguredFailed = true;
1036            proceed();
1037        } else {
1038            resetStateFromCurrentFragment();
1039        }
1040    }
1041
1042    @Override
1043    public void onCheckSettingsComplete() {
1044        mPreConfiguredFailed = false;
1045        mPasswordFailed = false;
1046        dismissCheckSettingsFragment();
1047        proceed();
1048    }
1049
1050    @Override
1051    public void onCheckSettingsAutoDiscoverComplete(int result) {
1052        dismissCheckSettingsFragment();
1053        proceed();
1054    }
1055
1056    @Override
1057    public void onCheckSettingsSecurityRequired(String hostName) {
1058        dismissCheckSettingsFragment();
1059        final DialogFragment f = SecurityRequiredDialogFragment.newInstance(hostName);
1060        f.show(getFragmentManager(), SecurityRequiredDialogFragment.TAG);
1061    }
1062
1063    @Override
1064    public void onSecurityRequiredDialogResult(boolean ok) {
1065        if (ok) {
1066            proceed();
1067        } else {
1068            resetStateFromCurrentFragment();
1069        }
1070    }
1071
1072    @Override
1073    public void onChooseProtocol(String protocol) {
1074        mSetupData.setIncomingProtocol(this, protocol);
1075        final Account account = mSetupData.getAccount();
1076        setDefaultsForProtocol(account);
1077        proceed();
1078    }
1079
1080    @Override
1081    public void onABProtocolDisambiguated(String chosenProtocol) {
1082        if (!TextUtils.equals(mSetupData.getIncomingProtocol(this), chosenProtocol)) {
1083            mIsPreConfiguredProvider = false;
1084            mSetupData.setIncomingProtocol(this, chosenProtocol);
1085            final Account account = mSetupData.getAccount();
1086            setDefaultsForProtocol(account);
1087        }
1088        proceed();
1089    }
1090
1091    /**
1092     * Ths is called when the user clicks the "done" button.
1093     * It collects the data from the UI, updates the setup account record, and launches a fragment
1094     * which handles creating the account in the system and database.
1095     */
1096    private void initiateAccountCreation() {
1097        mIsProcessing = true;
1098        getContentFragment().setNextButtonEnabled(false);
1099
1100        final Account account = mSetupData.getAccount();
1101        if (account.mHostAuthRecv == null) {
1102            throw new IllegalStateException("in AccountSetupOptions with null mHostAuthRecv");
1103        }
1104
1105        final AccountSetupOptionsFragment fragment = (AccountSetupOptionsFragment)
1106                getContentFragment();
1107        if (fragment == null) {
1108            throw new IllegalStateException("Fragment missing!");
1109        }
1110
1111        account.setDisplayName(account.getEmailAddress());
1112        int newFlags = account.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
1113        final EmailServiceUtils.EmailServiceInfo serviceInfo =
1114                mSetupData.getIncomingServiceInfo(this);
1115        if (serviceInfo.offerAttachmentPreload && fragment.getBackgroundAttachmentsValue()) {
1116            newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
1117        }
1118        final HostAuth hostAuth = account.getOrCreateHostAuthRecv(this);
1119        if (hostAuth.mProtocol.equals(getString(R.string.protocol_eas))) {
1120            try {
1121                final double protocolVersionDouble = Double.parseDouble(account.mProtocolVersion);
1122                if (protocolVersionDouble >= 12.0) {
1123                    // If the the account is EAS and the protocol version is above 12.0,
1124                    // we know that SmartForward is enabled and the various search flags
1125                    // should be enabled first.
1126                    // TODO: Move this into protocol specific code in the future.
1127                    newFlags |= Account.FLAGS_SUPPORTS_SMART_FORWARD |
1128                            Account.FLAGS_SUPPORTS_GLOBAL_SEARCH | Account.FLAGS_SUPPORTS_SEARCH;
1129                }
1130            } catch (NumberFormatException e) {
1131                LogUtils.wtf(LogUtils.TAG, e, "Exception thrown parsing the protocol version.");
1132            }
1133        }
1134        account.setFlags(newFlags);
1135        account.setSyncInterval(fragment.getCheckFrequencyValue());
1136        final Integer syncWindowValue = fragment.getAccountSyncWindowValue();
1137        if (syncWindowValue != null) {
1138            account.setSyncLookback(syncWindowValue);
1139        }
1140
1141        // Finish setting up the account, and commit it to the database
1142        if (mSetupData.getPolicy() != null) {
1143            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
1144            account.mPolicy = mSetupData.getPolicy();
1145        }
1146
1147        // Finally, write the completed account (for the first time) and then
1148        // install it into the Account manager as well.  These are done off-thread.
1149        // The account manager will report back via the callback, which will take us to
1150        // the next operations.
1151        final boolean syncEmail = fragment.getSyncEmailValue();
1152        final boolean syncCalendar = serviceInfo.syncCalendar && fragment.getSyncCalendarValue();
1153        final boolean syncContacts = serviceInfo.syncContacts && fragment.getSyncContactsValue();
1154        final boolean enableNotifications = fragment.getNotifyValue();
1155
1156        final Fragment f = AccountCreationFragment.newInstance(account, syncEmail, syncCalendar,
1157                syncContacts, enableNotifications);
1158        final FragmentTransaction ft = getFragmentManager().beginTransaction();
1159        ft.add(f, AccountCreationFragment.TAG);
1160        ft.commit();
1161
1162        showCreateAccountDialog();
1163    }
1164
1165    /**
1166     * Called by the account creation fragment after it has completed.
1167     * We do a small amount of work here before moving on to the next state.
1168     */
1169    @Override
1170    public void onAccountCreationFragmentComplete() {
1171        destroyAccountCreationFragment();
1172        // If the account manager initiated the creation, and success was not reported,
1173        // then we assume that we're giving up (for any reason) - report failure.
1174        if (mAccountAuthenticatorResponse != null) {
1175            final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
1176            final Bundle b = new Bundle(2);
1177            b.putString(AccountManager.KEY_ACCOUNT_NAME, mSetupData.getEmail());
1178            b.putString(AccountManager.KEY_ACCOUNT_TYPE, info.accountType);
1179            mAccountAuthenticatorResponse.onResult(b);
1180            mAccountAuthenticatorResponse = null;
1181            mReportAccountAuthenticatorError = false;
1182        }
1183        setResult(RESULT_OK);
1184        proceed();
1185    }
1186
1187    @Override
1188    public void destroyAccountCreationFragment() {
1189        dismissCreateAccountDialog();
1190
1191        final Fragment f = getFragmentManager().findFragmentByTag(AccountCreationFragment.TAG);
1192        if (f == null) {
1193            LogUtils.wtf(LogUtils.TAG, "Couldn't find AccountCreationFragment to destroy");
1194        }
1195        getFragmentManager().beginTransaction()
1196                .remove(f)
1197                .commit();
1198    }
1199
1200
1201    public static class CreateAccountDialogFragment extends DialogFragment {
1202        public static final String TAG = "CreateAccountDialogFragment";
1203        public CreateAccountDialogFragment() {}
1204
1205        public static CreateAccountDialogFragment newInstance() {
1206            return new CreateAccountDialogFragment();
1207        }
1208
1209        @Override
1210        public Dialog onCreateDialog(Bundle savedInstanceState) {
1211            /// Show "Creating account..." dialog
1212            setCancelable(false);
1213            final ProgressDialog d = new ProgressDialog(getActivity());
1214            d.setIndeterminate(true);
1215            d.setMessage(getString(R.string.account_setup_creating_account_msg));
1216            return d;
1217        }
1218    }
1219
1220    protected void showCreateAccountDialog() {
1221        CreateAccountDialogFragment.newInstance()
1222                .show(getFragmentManager(), CreateAccountDialogFragment.TAG);
1223    }
1224
1225    protected void dismissCreateAccountDialog() {
1226        final DialogFragment f = (DialogFragment)
1227                getFragmentManager().findFragmentByTag(CreateAccountDialogFragment.TAG);
1228        if (f != null) {
1229            f.dismiss();
1230        }
1231    }
1232
1233    public static class CreateAccountErrorDialogFragment extends DialogFragment
1234            implements DialogInterface.OnClickListener {
1235        public CreateAccountErrorDialogFragment() {}
1236
1237        @Override
1238        public Dialog onCreateDialog(Bundle savedInstanceState) {
1239            final String message = getString(R.string.account_setup_failed_dlg_auth_message,
1240                    R.string.system_account_create_failed);
1241
1242            setCancelable(false);
1243            return new AlertDialog.Builder(getActivity())
1244                    .setIconAttribute(android.R.attr.alertDialogIcon)
1245                    .setTitle(R.string.account_setup_failed_dlg_title)
1246                    .setMessage(message)
1247                    .setPositiveButton(android.R.string.ok, this)
1248                    .create();
1249        }
1250
1251        @Override
1252        public void onClick(DialogInterface dialog, int which) {
1253            getActivity().finish();
1254        }
1255    }
1256
1257    /**
1258     * This is called if MailService.setupAccountManagerAccount() fails for some reason
1259     */
1260    @Override
1261    public void showCreateAccountErrorDialog() {
1262        new CreateAccountErrorDialogFragment().show(getFragmentManager(), null);
1263    }
1264
1265    /**
1266     * Collect the data from AccountSetupNames and finish up account creation
1267     */
1268    private void initiateAccountFinalize() {
1269        mIsProcessing = true;
1270        getContentFragment().setNextButtonEnabled(false);
1271
1272        AccountSetupNamesFragment fragment = (AccountSetupNamesFragment) getContentFragment();
1273        // Update account object from UI
1274        final Account account = mSetupData.getAccount();
1275        final String description = fragment.getDescription();
1276        if (!TextUtils.isEmpty(description)) {
1277            account.setDisplayName(description);
1278        }
1279        account.setSenderName(fragment.getSenderName());
1280
1281        final Fragment f = AccountFinalizeFragment.newInstance(account);
1282        final FragmentTransaction ft = getFragmentManager().beginTransaction();
1283        ft.add(f, AccountFinalizeFragment.TAG);
1284        ft.commit();
1285    }
1286
1287    /**
1288     * Called when the AccountFinalizeFragment has finished its tasks
1289     */
1290    @Override
1291    public void onAccountFinalizeFragmentComplete() {
1292        finish();
1293    }
1294}
1295