1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity.setup;
18
19import android.accounts.AccountAuthenticatorResponse;
20import android.accounts.AccountManager;
21import android.app.Activity;
22import android.app.ActivityManager;
23import android.app.AlertDialog;
24import android.app.Dialog;
25import android.app.DialogFragment;
26import android.app.FragmentTransaction;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.os.AsyncTask;
31import android.os.Bundle;
32import android.text.Editable;
33import android.text.TextUtils;
34import android.text.TextWatcher;
35import android.view.View;
36import android.view.View.OnClickListener;
37import android.widget.Button;
38import android.widget.EditText;
39import android.widget.Toast;
40
41import com.android.email.EmailAddressValidator;
42import com.android.email.Preferences;
43import com.android.email.R;
44import com.android.email.activity.ActivityHelper;
45import com.android.email.activity.UiUtilities;
46import com.android.email.service.EmailServiceUtils;
47import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
48import com.android.emailcommon.Logging;
49import com.android.emailcommon.VendorPolicyLoader.Provider;
50import com.android.emailcommon.provider.Account;
51import com.android.emailcommon.provider.EmailContent;
52import com.android.emailcommon.provider.HostAuth;
53import com.android.emailcommon.service.ServiceProxy;
54import com.android.emailcommon.utility.EmailAsyncTask;
55import com.android.emailcommon.utility.Utility;
56import com.android.mail.utils.LogUtils;
57
58import java.net.URISyntaxException;
59import java.util.concurrent.Callable;
60import java.util.concurrent.ExecutionException;
61import java.util.concurrent.FutureTask;
62
63/**
64 * Prompts the user for the email address and password. Also prompts for "Use this account as
65 * default" if this is the 2nd+ account being set up.
66 *
67 * If the domain is well-known, the account is configured fully and checked immediately
68 * using AccountCheckSettingsFragment.  If this succeeds we proceed directly to AccountSetupOptions.
69 *
70 * If the domain is not known, or the user selects Manual setup, we invoke the
71 * AccountSetupAccountType activity where the user can begin to manually configure the account.
72 *
73 * === Support for automated testing ==
74 * This activity can also be launched directly via ACTION_CREATE_ACCOUNT.  This is intended
75 * only for use by continuous test systems, and is currently only available when
76 * {@link ActivityManager#isRunningInTestHarness()} is set.  To use this mode, you must construct
77 * an intent which contains all necessary information to create the account.  No connection
78 * checking is done, so the account may or may not actually work.  Here is a sample command, for a
79 * gmail account "test_account" with a password of "test_password".
80 *
81 *      $ adb shell am start -a com.android.email.CREATE_ACCOUNT \
82 *          -e EMAIL test_account@gmail.com \
83 *          -e USER "Test Account Name" \
84 *          -e INCOMING imap+ssl+://test_account:test_password@imap.gmail.com \
85 *          -e OUTGOING smtp+ssl+://test_account:test_password@smtp.gmail.com
86 *
87 * Note: For accounts that require the full email address in the login, encode the @ as %40.
88 * Note: Exchange accounts that require device security policies cannot be created automatically.
89 */
90public class AccountSetupBasics extends AccountSetupActivity
91        implements OnClickListener, TextWatcher, AccountCheckSettingsFragment.Callbacks {
92
93    // Set to false before shipping, logs PII
94    private final static boolean ENTER_DEBUG_SCREEN = false;
95
96    /**
97     * Direct access for forcing account creation
98     * For use by continuous automated test system (e.g. in conjunction with monkey tests)
99     */
100    private static final String ACTION_CREATE_ACCOUNT = "com.android.email.CREATE_ACCOUNT";
101    private static final String EXTRA_FLOW_MODE = "FLOW_MODE";
102    private static final String EXTRA_FLOW_ACCOUNT_TYPE = "FLOW_ACCOUNT_TYPE";
103    private static final String EXTRA_CREATE_ACCOUNT_EMAIL = "EMAIL";
104    private static final String EXTRA_CREATE_ACCOUNT_USER = "USER";
105    private static final String EXTRA_CREATE_ACCOUNT_INCOMING = "INCOMING";
106    private static final String EXTRA_CREATE_ACCOUNT_OUTGOING = "OUTGOING";
107    private static final Boolean DEBUG_ALLOW_NON_TEST_HARNESS_CREATION = false;
108
109    private static final String STATE_KEY_PROVIDER = "AccountSetupBasics.provider";
110
111    // Support for UI
112    private EditText mEmailView;
113    private EditText mPasswordView;
114    private final EmailAddressValidator mEmailValidator = new EmailAddressValidator();
115    private Provider mProvider;
116    private Button mManualButton;
117    private Button mNextButton;
118    private boolean mNextButtonInhibit;
119    private boolean mPaused;
120    private boolean mReportAccountAuthenticatorError;
121
122    // FutureTask to look up the owner
123    FutureTask<String> mOwnerLookupTask;
124
125    public static void actionNewAccount(Activity fromActivity) {
126        final Intent i = new Intent(fromActivity, AccountSetupBasics.class);
127        i.putExtra(EXTRA_FLOW_MODE, SetupData.FLOW_MODE_NORMAL);
128        fromActivity.startActivity(i);
129    }
130
131    public static void actionNewAccountWithResult(Activity fromActivity) {
132        final Intent i = new ForwardingIntent(fromActivity, AccountSetupBasics.class);
133        i.putExtra(EXTRA_FLOW_MODE, SetupData.FLOW_MODE_NO_ACCOUNTS);
134        fromActivity.startActivity(i);
135    }
136
137    /**
138     * This generates setup data that can be used to start a self-contained account creation flow
139     * for exchange accounts.
140     */
141    public static Intent actionGetCreateAccountIntent(Context context, String accountManagerType) {
142        final Intent i = new Intent(context, AccountSetupBasics.class);
143        i.putExtra(EXTRA_FLOW_MODE, SetupData.FLOW_MODE_ACCOUNT_MANAGER);
144        i.putExtra(EXTRA_FLOW_ACCOUNT_TYPE, accountManagerType);
145        return i;
146    }
147
148    public static void actionAccountCreateFinishedAccountFlow(Activity fromActivity) {
149        // TODO: handle this case - modifying state on SetupData when instantiating an Intent
150        // is not safe, since it's not guaranteed that an Activity will run with the Intent, and
151        // information can get lost.
152
153        final Intent i= new ForwardingIntent(fromActivity, AccountSetupBasics.class);
154        // If we're in the "account flow" (from AccountManager), we want to return to the caller
155        // (in the settings app)
156        i.putExtra(SetupData.EXTRA_SETUP_DATA, new SetupData(SetupData.FLOW_MODE_RETURN_TO_CALLER));
157        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
158        fromActivity.startActivity(i);
159    }
160
161    public static void actionAccountCreateFinishedWithResult(Activity fromActivity) {
162        // TODO: handle this case - modifying state on SetupData when instantiating an Intent
163        // is not safe, since it's not guaranteed that an Activity will run with the Intent, and
164        // information can get lost.
165
166        final Intent i= new ForwardingIntent(fromActivity, AccountSetupBasics.class);
167        // If we're in the "no accounts" flow, we want to return to the caller with a result
168        i.putExtra(SetupData.EXTRA_SETUP_DATA,
169                new SetupData(SetupData.FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT));
170        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
171        fromActivity.startActivity(i);
172    }
173
174    public static void actionAccountCreateFinished(final Activity fromActivity, Account account) {
175        final Intent i = new Intent(fromActivity, AccountSetupBasics.class);
176        // If we're not in the "account flow" (from AccountManager), we want to show the
177        // message list for the new inbox
178        i.putExtra(SetupData.EXTRA_SETUP_DATA,
179                new SetupData(SetupData.FLOW_MODE_RETURN_TO_MESSAGE_LIST, account));
180        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
181        fromActivity.startActivity(i);
182    }
183
184    @Override
185    public void onCreate(Bundle savedInstanceState) {
186        super.onCreate(savedInstanceState);
187        ActivityHelper.debugSetWindowFlags(this);
188
189        // Check for forced account creation first, as it comes from an externally-generated
190        // intent and won't have any SetupData prepared.
191        final Intent intent = getIntent();
192        final String action = intent.getAction();
193
194        if (ServiceProxy.getIntentStringForEmailPackage(
195                this, ACTION_CREATE_ACCOUNT).equals(action)) {
196            mSetupData = new SetupData(SetupData.FLOW_MODE_FORCE_CREATE);
197        } else {
198            final int intentFlowMode =
199                    intent.getIntExtra(EXTRA_FLOW_MODE, SetupData.FLOW_MODE_UNSPECIFIED);
200            if (intentFlowMode != SetupData.FLOW_MODE_UNSPECIFIED) {
201                mSetupData = new SetupData(intentFlowMode,
202                        intent.getStringExtra(EXTRA_FLOW_ACCOUNT_TYPE));
203            }
204        }
205
206        final int flowMode = mSetupData.getFlowMode();
207        if (flowMode == SetupData.FLOW_MODE_RETURN_TO_CALLER) {
208            // Return to the caller who initiated account creation
209            finish();
210            return;
211        } else if (flowMode == SetupData.FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT) {
212            if (EmailContent.count(this, Account.CONTENT_URI) > 0) {
213                setResult(RESULT_OK);
214            } else {
215                setResult(RESULT_CANCELED);
216            }
217            finish();
218            return;
219        } else if (flowMode == SetupData.FLOW_MODE_RETURN_TO_MESSAGE_LIST) {
220            final Account account = mSetupData.getAccount();
221            if (account != null && account.mId >= 0) {
222                // Show the message list for the new account
223                //***
224                //Welcome.actionOpenAccountInbox(this, account.mId);
225                finish();
226                return;
227            }
228        }
229
230        setContentView(R.layout.account_setup_basics);
231
232        mEmailView = UiUtilities.getView(this, R.id.account_email);
233        mPasswordView = UiUtilities.getView(this, R.id.account_password);
234
235        mEmailView.addTextChangedListener(this);
236        mPasswordView.addTextChangedListener(this);
237
238        // Configure buttons
239        mManualButton = UiUtilities.getView(this, R.id.manual_setup);
240        mNextButton = UiUtilities.getView(this, R.id.next);
241        mManualButton.setVisibility(View.VISIBLE);
242        mManualButton.setOnClickListener(this);
243        mNextButton.setOnClickListener(this);
244        // Force disabled until validator notifies otherwise
245        onEnableProceedButtons(false);
246        // Lightweight debounce while Async tasks underway
247        mNextButtonInhibit = false;
248
249        // Set aside incoming AccountAuthenticatorResponse, if there was any
250        final AccountAuthenticatorResponse authenticatorResponse =
251            getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
252        mSetupData.setAccountAuthenticatorResponse(authenticatorResponse);
253        if (authenticatorResponse != null) {
254            // When this Activity is called as part of account authentification flow,
255            // we are responsible for eventually reporting the result (success or failure) to
256            // the account manager.  Most exit paths represent an failed or abandoned setup,
257            // so the default is to report the error.  Success will be reported by the code in
258            // AccountSetupOptions that commits the finally created account.
259            mReportAccountAuthenticatorError = true;
260        }
261
262        // Load fields, but only once
263        final String userName = mSetupData.getUsername();
264        if (userName != null) {
265            mEmailView.setText(userName);
266            mSetupData.setUsername(null);
267        }
268        final String password = mSetupData.getPassword();
269        if (userName != null) {
270            mPasswordView.setText(password);
271            mSetupData.setPassword(null);
272        }
273
274        // Handle force account creation immediately (now that fragment is set up)
275        // This is never allowed in a normal user build and will exit immediately.
276        if (mSetupData.getFlowMode() == SetupData.FLOW_MODE_FORCE_CREATE) {
277            if (!DEBUG_ALLOW_NON_TEST_HARNESS_CREATION &&
278                    !ActivityManager.isRunningInTestHarness()) {
279                LogUtils.e(Logging.LOG_TAG,
280                        "ERROR: Force account create only allowed while in test harness");
281                finish();
282                return;
283            }
284            final String email = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_EMAIL);
285            final String user = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_USER);
286            final String incoming = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_INCOMING);
287            final String outgoing = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_OUTGOING);
288            if (TextUtils.isEmpty(email) || TextUtils.isEmpty(user) ||
289                    TextUtils.isEmpty(incoming) || TextUtils.isEmpty(outgoing)) {
290                LogUtils.e(Logging.LOG_TAG, "ERROR: Force account create requires extras EMAIL, " +
291                        "USER, INCOMING, OUTGOING");
292                finish();
293                return;
294            }
295            forceCreateAccount(email, user, incoming, outgoing);
296            // calls finish
297            onCheckSettingsComplete(AccountCheckSettingsFragment.CHECK_SETTINGS_OK, mSetupData);
298            return;
299        }
300
301        if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
302            mProvider = (Provider) savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
303        }
304
305        // Launch a worker to look up the owner name.  It should be ready well in advance of
306        // the time the user clicks next or manual.
307        mOwnerLookupTask = new FutureTask<String>(mOwnerLookupCallable);
308        EmailAsyncTask.runAsyncParallel(mOwnerLookupTask);
309    }
310
311    @Override
312    public void onPause() {
313        super.onPause();
314        mPaused = true;
315    }
316
317    @Override
318    public void onResume() {
319        super.onResume();
320        mPaused = false;
321    }
322
323    @Override
324    public void finish() {
325        // If the account manager initiated the creation, and success was not reported,
326        // then we assume that we're giving up (for any reason) - report failure.
327        if (mReportAccountAuthenticatorError) {
328            final AccountAuthenticatorResponse authenticatorResponse =
329                    mSetupData.getAccountAuthenticatorResponse();
330            if (authenticatorResponse != null) {
331                authenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
332                mSetupData.setAccountAuthenticatorResponse(null);
333            }
334        }
335        super.finish();
336    }
337
338    @Override
339    public void onSaveInstanceState(Bundle outState) {
340        super.onSaveInstanceState(outState);
341        if (mProvider != null) {
342            outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
343        }
344    }
345
346    /**
347     * Implements OnClickListener
348     */
349    @Override
350    public void onClick(View v) {
351        switch (v.getId()) {
352            case R.id.next:
353                // Simple debounce - just ignore while async checks are underway
354                if (mNextButtonInhibit) {
355                    return;
356                }
357                onNext();
358                break;
359            case R.id.manual_setup:
360                onManualSetup(false);
361                break;
362        }
363    }
364
365    /**
366     * Implements TextWatcher
367     */
368    @Override
369    public void afterTextChanged(Editable s) {
370        validateFields();
371    }
372
373    /**
374     * Implements TextWatcher
375     */
376    @Override
377    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
378    }
379
380    /**
381     * Implements TextWatcher
382     */
383    @Override
384    public void onTextChanged(CharSequence s, int start, int before, int count) {
385    }
386
387    private void validateFields() {
388        final boolean valid = !TextUtils.isEmpty(mEmailView.getText())
389                && !TextUtils.isEmpty(mPasswordView.getText())
390                && mEmailValidator.isValid(mEmailView.getText().toString().trim());
391        onEnableProceedButtons(valid);
392
393        // Warn (but don't prevent) if password has leading/trailing spaces
394        AccountSettingsUtils.checkPasswordSpaces(this, mPasswordView);
395    }
396
397    /**
398     * Return an existing username if found, or null.  This is the result of the Callable (below).
399     */
400    private String getOwnerName() {
401        try {
402            return mOwnerLookupTask.get();
403        } catch (InterruptedException e) {
404            return null;
405        } catch (ExecutionException e) {
406            return null;
407        }
408    }
409
410    /**
411     * Callable that returns the username (based on other accounts) or null.
412     */
413    private final Callable<String> mOwnerLookupCallable = new Callable<String>() {
414        @Override
415        public String call() {
416            final Context context = AccountSetupBasics.this;
417
418            final long lastUsedAccountId =
419                    Preferences.getPreferences(context).getLastUsedAccountId();
420            final long defaultId = Account.getDefaultAccountId(context, lastUsedAccountId);
421
422            if (defaultId != -1) {
423                final Account account = Account.restoreAccountWithId(context, defaultId);
424                if (account != null) {
425                    return account.getSenderName();
426                }
427            }
428
429            return null;
430        }
431    };
432
433    /**
434     * Finish the auto setup process, in some cases after showing a warning dialog.
435     */
436    private void finishAutoSetup() {
437        final String email = mEmailView.getText().toString().trim();
438        final String password = mPasswordView.getText().toString();
439
440        try {
441            mProvider.expandTemplates(email);
442
443            final Account account = mSetupData.getAccount();
444            final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
445            HostAuth.setHostAuthFromString(recvAuth, mProvider.incomingUri);
446
447            recvAuth.setLogin(mProvider.incomingUsername, password);
448            final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this,
449                    recvAuth.mProtocol);
450            recvAuth.mPort =
451                    ((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) ? info.portSsl : info.port;
452
453            final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
454            HostAuth.setHostAuthFromString(sendAuth, mProvider.outgoingUri);
455            sendAuth.setLogin(mProvider.outgoingUsername, password);
456
457            // Populate the setup data, assuming that the duplicate account check will succeed
458            populateSetupData(getOwnerName(), email);
459
460            // Stop here if the login credentials duplicate an existing account
461            // Launch an Async task to do the work
462            new DuplicateCheckTask(this, email, true)
463                    .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
464        } catch (URISyntaxException e) {
465            /*
466             * If there is some problem with the URI we give up and go on to manual setup.
467             * Technically speaking, AutoDiscover is OK here, since the user clicked "Next"
468             * to get here. This will not happen in practice because we don't expect to
469             * find any EAS accounts in the providers list.
470             */
471            onManualSetup(true);
472        }
473    }
474
475    /**
476     * Async task that continues the work of finishAutoSetup().  Checks for a duplicate
477     * account and then either alerts the user, or continues.
478     */
479    private class DuplicateCheckTask extends AsyncTask<Void, Void, String> {
480        private final Context mContext;
481        private final String mCheckAddress;
482        private final boolean mAutoSetup;
483
484        public DuplicateCheckTask(Context context, String checkAddress,
485                boolean autoSetup) {
486            mContext = context;
487            mCheckAddress = checkAddress;
488            // Prevent additional clicks on the next button during Async lookup
489            mNextButtonInhibit = true;
490            mAutoSetup = autoSetup;
491        }
492
493        @Override
494        protected String doInBackground(Void... params) {
495            return Utility.findExistingAccount(mContext, null, mCheckAddress);
496        }
497
498        @Override
499        protected void onPostExecute(String duplicateAccountName) {
500            mNextButtonInhibit = false;
501            // Exit immediately if the user left before we finished
502            if (mPaused) return;
503            // Show duplicate account warning, or proceed
504            if (duplicateAccountName != null) {
505                final DuplicateAccountDialogFragment dialogFragment =
506                    DuplicateAccountDialogFragment.newInstance(duplicateAccountName);
507                dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG);
508            } else {
509                if (mAutoSetup) {
510                    final AccountCheckSettingsFragment checkerFragment =
511                        AccountCheckSettingsFragment.newInstance(
512                            SetupData.CHECK_INCOMING | SetupData.CHECK_OUTGOING, null);
513                    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
514                    transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG);
515                    transaction.addToBackStack("back");
516                    transaction.commit();
517                } else {
518                    onManualSetup(true);
519                }
520            }
521        }
522
523        @Override
524        protected void onCancelled(String s) {
525            mNextButtonInhibit = false;
526            LogUtils.d(LogUtils.TAG, "DuplicateCheckTask cancelled (AccountSetupBasics)");
527        }
528    }
529
530    /**
531     * When "next" button is clicked
532     */
533    private void onNext() {
534        // Try auto-configuration from XML providers (unless in EAS mode, we can skip it)
535        final String email = mEmailView.getText().toString().trim();
536        final String[] emailParts = email.split("@");
537        final String domain = emailParts[1].trim();
538        mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
539        if (mProvider != null) {
540            mProvider.expandTemplates(email);
541            if (mProvider.note != null) {
542                final NoteDialogFragment dialogFragment =
543                        NoteDialogFragment.newInstance(mProvider.note);
544                dialogFragment.show(getFragmentManager(), NoteDialogFragment.TAG);
545            } else {
546                finishAutoSetup();
547            }
548        } else {
549        // Can't use auto setup (although EAS accounts may still be able to AutoDiscover)
550            new DuplicateCheckTask(this, email, false)
551                    .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
552        }
553    }
554
555    /**
556     * When "manual setup" button is clicked
557     *
558     * @param allowAutoDiscover - true if the user clicked 'next' and (if the account is EAS)
559     * it's OK to use autodiscover.  false to prevent autodiscover and go straight to manual setup.
560     * Ignored for IMAP & POP accounts.
561     */
562    private void onManualSetup(boolean allowAutoDiscover) {
563        final String email = mEmailView.getText().toString().trim();
564        final String password = mPasswordView.getText().toString();
565        final String[] emailParts = email.split("@");
566        final String user = emailParts[0].trim();
567        final String domain = emailParts[1].trim();
568
569        // Alternate entry to the debug options screen (for devices without a physical keyboard:
570        //  Username: d@d.d
571        //  Password: debug
572        if (ENTER_DEBUG_SCREEN && "d@d.d".equals(email) && "debug".equals(password)) {
573            mEmailView.setText("");
574            mPasswordView.setText("");
575            AccountSettings.actionSettingsWithDebug(this);
576            return;
577        }
578
579        final Account account = mSetupData.getAccount();
580        final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
581        recvAuth.setLogin(user, password);
582        recvAuth.setConnection(null, domain, HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE);
583
584        final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
585        sendAuth.setLogin(user, password);
586        sendAuth.setConnection(null, domain, HostAuth.PORT_UNKNOWN, HostAuth.FLAG_NONE);
587
588        populateSetupData(getOwnerName(), email);
589
590        mSetupData.setAllowAutodiscover(allowAutoDiscover);
591        AccountSetupType.actionSelectAccountType(this, mSetupData);
592    }
593
594    /**
595     * To support continuous testing, we allow the forced creation of accounts.
596     * This works in a manner fairly similar to automatic setup, in which the complete server
597     * Uri's are available, except that we will also skip checking (as if both checks were true)
598     * and all other UI.
599     *
600     * @param email The email address for the new account
601     * @param user The user name for the new account
602     * @param incoming The URI-style string defining the incoming account
603     * @param outgoing The URI-style string defining the outgoing account
604     */
605    private void forceCreateAccount(String email, String user, String incoming, String outgoing) {
606        Account account = mSetupData.getAccount();
607        try {
608            final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
609            HostAuth.setHostAuthFromString(recvAuth, incoming);
610
611            final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
612            HostAuth.setHostAuthFromString(sendAuth, outgoing);
613
614            populateSetupData(user, email);
615        } catch (URISyntaxException e) {
616            // If we can't set up the URL, don't continue - account setup pages will fail too
617            Toast.makeText(
618                    this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG).show();
619        }
620    }
621
622    public static void setDefaultsForProtocol(Context context, Account account) {
623        final String protocol = account.mHostAuthRecv.mProtocol;
624        if (protocol == null) return;
625        final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
626        account.mSyncInterval = info.defaultSyncInterval;
627        account.mSyncLookback = info.defaultLookback;
628        if (info.offerLocalDeletes) {
629            account.setDeletePolicy(info.defaultLocalDeletes);
630        }
631    }
632
633    /**
634     * Populate SetupData's account with complete setup info.
635     */
636    private void populateSetupData(String senderName, String senderEmail) {
637        final Account account = mSetupData.getAccount();
638        account.setSenderName(senderName);
639        account.setEmailAddress(senderEmail);
640        account.setDisplayName(senderEmail);
641        setDefaultsForProtocol(this, account);
642    }
643
644    /**
645     * Implements AccountCheckSettingsFragment.Callbacks
646     *
647     * This is used in automatic setup mode to jump directly down to the options screen.
648     *
649     * This is the only case where we finish() this activity but account setup is continuing,
650     * so we inhibit reporting any error back to the Account manager.
651     */
652    @Override
653    public void onCheckSettingsComplete(int result, SetupData setupData) {
654        mSetupData = setupData;
655        if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) {
656            AccountSetupOptions.actionOptions(this, mSetupData);
657            mReportAccountAuthenticatorError = false;
658            finish();
659        }
660    }
661
662    /**
663     * Implements AccountCheckSettingsFragment.Callbacks
664     * This is overridden only by AccountSetupIncoming
665     */
666    @Override
667    public void onAutoDiscoverComplete(int result, SetupData setupData) {
668        throw new IllegalStateException();
669    }
670
671    private void onEnableProceedButtons(boolean enabled) {
672        mManualButton.setEnabled(enabled);
673        mNextButton.setEnabled(enabled);
674    }
675
676    /**
677     * Dialog fragment to show "setup note" dialog
678     */
679    public static class NoteDialogFragment extends DialogFragment {
680        final static String TAG = "NoteDialogFragment";
681
682        // Argument bundle keys
683        private final static String BUNDLE_KEY_NOTE = "NoteDialogFragment.Note";
684
685        // Public no-args constructor needed for fragment re-instantiation
686        public NoteDialogFragment() {}
687
688        /**
689         * Create the dialog with parameters
690         */
691        public static NoteDialogFragment newInstance(String note) {
692            final NoteDialogFragment f = new NoteDialogFragment();
693            final Bundle b = new Bundle(1);
694            b.putString(BUNDLE_KEY_NOTE, note);
695            f.setArguments(b);
696            return f;
697        }
698
699        @Override
700        public Dialog onCreateDialog(Bundle savedInstanceState) {
701            final Context context = getActivity();
702            final String note = getArguments().getString(BUNDLE_KEY_NOTE);
703
704            return new AlertDialog.Builder(context)
705                .setIconAttribute(android.R.attr.alertDialogIcon)
706                .setTitle(android.R.string.dialog_alert_title)
707                .setMessage(note)
708                .setPositiveButton(
709                        R.string.okay_action,
710                        new DialogInterface.OnClickListener() {
711                            @Override
712                            public void onClick(DialogInterface dialog, int which) {
713                                final Activity a = getActivity();
714                                if (a instanceof AccountSetupBasics) {
715                                    ((AccountSetupBasics)a).finishAutoSetup();
716                                }
717                                dismiss();
718                            }
719                        })
720                .setNegativeButton(
721                        context.getString(R.string.cancel_action),
722                        null)
723                .create();
724        }
725    }
726}
727