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