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