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