• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityManager;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.app.DialogFragment;
25 import android.app.Fragment;
26 import android.app.FragmentManager;
27 import android.app.FragmentTransaction;
28 import android.app.LoaderManager;
29 import android.app.ProgressDialog;
30 import android.content.Context;
31 import android.content.CursorLoader;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.Loader;
35 import android.database.Cursor;
36 import android.os.Bundle;
37 import android.provider.ContactsContract;
38 import android.text.TextUtils;
39 import android.view.View;
40 import android.view.inputmethod.InputMethodManager;
41 import android.widget.Toast;
42 
43 import com.android.email.R;
44 import com.android.email.service.EmailServiceUtils;
45 import com.android.emailcommon.VendorPolicyLoader;
46 import com.android.emailcommon.provider.Account;
47 import com.android.emailcommon.provider.EmailContent.AccountColumns;
48 import com.android.emailcommon.provider.HostAuth;
49 import com.android.emailcommon.service.SyncWindow;
50 import com.android.mail.utils.LogUtils;
51 
52 import java.net.URISyntaxException;
53 import java.util.HashMap;
54 import java.util.Map;
55 
56 public 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 
actionNewAccountIntent(final Context context)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 
actionNewAccountWithResultIntent(final Context context)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 
actionGetCreateAccountIntent(final Context context, final String accountManagerType)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
onCreate(Bundle savedInstanceState)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
onResume()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
onSaveInstanceState(Bundle outState)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      */
updateContentFragment(boolean addToBackstack)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      */
getContentFragment()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      */
resetStateFromCurrentFragment()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      */
proceed()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      */
possiblyDivertToGmail()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
onBackPressed()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
setAccount(Account account)728     public void setAccount(Account account) {
729         mSetupData.setAccount(account);
730     }
731 
732     @Override
finish()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
onNextButton()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      */
onBasicsComplete()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 
showDuplicateAccountDialog(final String existingAccountName)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
onDuplicateAccountDialogDismiss()805     public void onDuplicateAccountDialogDismiss() {
806         resetStateFromCurrentFragment();
807     }
808 
shouldDivertToManual()809     private boolean shouldDivertToManual() {
810         final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment();
811         return f.isManualSetup();
812     }
813 
814     @Override
onCredentialsComplete(Bundle results)815     public void onCredentialsComplete(Bundle results) {
816         proceed();
817     }
818 
collectCredentials()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
onNoteDialogComplete()839     public void onNoteDialogComplete() {
840         finishAutoSetup();
841         proceed();
842     }
843 
844     @Override
onNoteDialogCancel()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      */
finishAutoSetup()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      */
setDefaultsForProtocol(Account account)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      */
populateSetupData(String senderName, String senderEmail)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 
onIncomingComplete()929     private void onIncomingComplete() {
930         AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment();
931         f.collectUserInput();
932     }
933 
onOutgoingComplete()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
onAccountServerUIComplete(int checkMode)941     public void onAccountServerUIComplete(int checkMode) {}
942 
943     // This callback method is only applicable to using Incoming/Outgoing fragments in settings mode
944     @Override
onAccountServerSaveComplete()945     public void onAccountServerSaveComplete() {}
946 
populateHostAuthsFromSetupData()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 
initiateAutoDiscover()975     private void initiateAutoDiscover() {
976         // Populate the setup data, assuming that the duplicate account check will succeed
977         initiateCheckSettingsFragment(SetupDataFragment.CHECK_AUTODISCOVER);
978     }
979 
initiateCheckSettingsFragment(int checkMode)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
onCheckSettingsProgressDialogCancel()990     public void onCheckSettingsProgressDialogCancel() {
991         dismissCheckSettingsFragment();
992         resetStateFromCurrentFragment();
993     }
994 
dismissCheckSettingsFragment()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
onCheckSettingsError(int reason, String message)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
onCheckSettingsErrorDialogEditCertificate()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
onCheckSettingsErrorDialogEditSettings()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
onCheckSettingsComplete()1043     public void onCheckSettingsComplete() {
1044         mPreConfiguredFailed = false;
1045         mPasswordFailed = false;
1046         dismissCheckSettingsFragment();
1047         proceed();
1048     }
1049 
1050     @Override
onCheckSettingsAutoDiscoverComplete(int result)1051     public void onCheckSettingsAutoDiscoverComplete(int result) {
1052         dismissCheckSettingsFragment();
1053         proceed();
1054     }
1055 
1056     @Override
onCheckSettingsSecurityRequired(String hostName)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
onSecurityRequiredDialogResult(boolean ok)1064     public void onSecurityRequiredDialogResult(boolean ok) {
1065         if (ok) {
1066             proceed();
1067         } else {
1068             resetStateFromCurrentFragment();
1069         }
1070     }
1071 
1072     @Override
onChooseProtocol(String protocol)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
onABProtocolDisambiguated(String chosenProtocol)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      */
initiateAccountCreation()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
onAccountCreationFragmentComplete()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
destroyAccountCreationFragment()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";
CreateAccountDialogFragment()1203         public CreateAccountDialogFragment() {}
1204 
newInstance()1205         public static CreateAccountDialogFragment newInstance() {
1206             return new CreateAccountDialogFragment();
1207         }
1208 
1209         @Override
onCreateDialog(Bundle savedInstanceState)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 
showCreateAccountDialog()1220     protected void showCreateAccountDialog() {
1221         CreateAccountDialogFragment.newInstance()
1222                 .show(getFragmentManager(), CreateAccountDialogFragment.TAG);
1223     }
1224 
dismissCreateAccountDialog()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 {
CreateAccountErrorDialogFragment()1235         public CreateAccountErrorDialogFragment() {}
1236 
1237         @Override
onCreateDialog(Bundle savedInstanceState)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
onClick(DialogInterface dialog, int which)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
showCreateAccountErrorDialog()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      */
initiateAccountFinalize()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
onAccountFinalizeFragmentComplete()1291     public void onAccountFinalizeFragmentComplete() {
1292         finish();
1293     }
1294 }
1295