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