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