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