AccountSetupBasics.java revision ecb1af804144689d4ead96a247b565f9b4eb8160
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity.setup; 18 19import com.android.email.AccountBackupRestore; 20import com.android.email.Email; 21import com.android.email.EmailAddressValidator; 22import com.android.email.R; 23import com.android.email.Utility; 24import com.android.email.VendorPolicyLoader; 25import com.android.email.activity.Debug; 26import com.android.email.activity.MessageList; 27import com.android.email.provider.EmailContent; 28import com.android.email.provider.EmailContent.Account; 29import com.android.email.provider.EmailContent.Mailbox; 30 31import android.app.Activity; 32import android.app.AlertDialog; 33import android.app.Dialog; 34import android.content.Context; 35import android.content.DialogInterface; 36import android.content.Intent; 37import android.content.res.XmlResourceParser; 38import android.database.Cursor; 39import android.os.Bundle; 40import android.text.Editable; 41import android.text.TextWatcher; 42import android.util.Log; 43import android.view.View; 44import android.view.View.OnClickListener; 45import android.widget.Button; 46import android.widget.CheckBox; 47import android.widget.EditText; 48import android.widget.TextView; 49import android.widget.Toast; 50 51import java.io.Serializable; 52import java.net.URI; 53import java.net.URISyntaxException; 54 55/** 56 * Prompts the user for the email address and password. Also prompts for 57 * "Use this account as default" if this is the 2nd+ account being set up. 58 * Attempts to lookup default settings for the domain the user specified. If the 59 * domain is known the settings are handed off to the AccountSetupCheckSettings 60 * activity. If no settings are found the settings are handed off to the 61 * AccountSetupAccountType activity. 62 */ 63public class AccountSetupBasics extends Activity 64 implements OnClickListener, TextWatcher { 65 private final static boolean ENTER_DEBUG_SCREEN = true; 66 67 private final static String EXTRA_ACCOUNT = "com.android.email.AccountSetupBasics.account"; 68 public final static String EXTRA_EAS_FLOW = "com.android.email.extra.eas_flow"; 69 70 // Action asking us to return to our original caller (i.e. finish) 71 private static final String ACTION_RETURN_TO_CALLER = 72 "com.android.email.AccountSetupBasics.return"; 73 // Action asking us to restart the task from the Welcome activity (which will figure out the 74 // right place to go) and finish 75 private static final String ACTION_START_AT_MESSAGE_LIST = 76 "com.android.email.AccountSetupBasics.messageList"; 77 78 private final static String EXTRA_USERNAME = "com.android.email.AccountSetupBasics.username"; 79 private final static String EXTRA_PASSWORD = "com.android.email.AccountSetupBasics.password"; 80 81 private final static int DIALOG_NOTE = 1; 82 private final static int DIALOG_DUPLICATE_ACCOUNT = 2; 83 84 private final static String STATE_KEY_PROVIDER = 85 "com.android.email.AccountSetupBasics.provider"; 86 87 // NOTE: If you change this value, confirm that the new interval exists in arrays.xml 88 private final static int DEFAULT_ACCOUNT_CHECK_INTERVAL = 15; 89 90 private EditText mEmailView; 91 private EditText mPasswordView; 92 private CheckBox mDefaultView; 93 private Button mNextButton; 94 private Button mManualSetupButton; 95 private EmailContent.Account mAccount; 96 private Provider mProvider; 97 private boolean mEasFlowMode; 98 private String mDuplicateAccountName; 99 100 private EmailAddressValidator mEmailValidator = new EmailAddressValidator(); 101 102 public static void actionNewAccount(Activity fromActivity) { 103 Intent i = new Intent(fromActivity, AccountSetupBasics.class); 104 fromActivity.startActivity(i); 105 } 106 107 public static void actionNewAccountWithCredentials(Activity fromActivity, 108 String username, String password, boolean easFlow) { 109 Intent i = new Intent(fromActivity, AccountSetupBasics.class); 110 i.putExtra(EXTRA_USERNAME, username); 111 i.putExtra(EXTRA_PASSWORD, password); 112 i.putExtra(EXTRA_EAS_FLOW, easFlow); 113 fromActivity.startActivity(i); 114 } 115 116 /** 117 * This creates an intent that can be used to start a self-contained account creation flow 118 * for exchange accounts. 119 */ 120 public static Intent actionSetupExchangeIntent(Context context) { 121 Intent i = new Intent(context, AccountSetupBasics.class); 122 i.putExtra(EXTRA_EAS_FLOW, true); 123 return i; 124 } 125 126 public static void actionAccountCreateFinishedEas(Activity fromActivity) { 127 Intent i= new Intent(fromActivity, AccountSetupBasics.class); 128 // If we're in the "eas flow" (from AccountManager), we want to return to the caller 129 // (in the settings app) 130 i.putExtra(AccountSetupBasics.ACTION_RETURN_TO_CALLER, true); 131 i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 132 fromActivity.startActivity(i); 133 } 134 135 public static void actionAccountCreateFinished(Activity fromActivity, long accountId) { 136 Intent i= new Intent(fromActivity, AccountSetupBasics.class); 137 // If we're not in the "eas flow" (from AccountManager), we want to show the message list 138 // for the new inbox 139 i.putExtra(AccountSetupBasics.ACTION_START_AT_MESSAGE_LIST, accountId); 140 i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 141 fromActivity.startActivity(i); 142 } 143 144 @Override 145 public void onCreate(Bundle savedInstanceState) { 146 super.onCreate(savedInstanceState); 147 148 Intent intent = getIntent(); 149 if (intent.getBooleanExtra(ACTION_RETURN_TO_CALLER, false)) { 150 // Return to the caller who initiated account creation 151 finish(); 152 return; 153 } else { 154 long accountId = intent.getLongExtra(ACTION_START_AT_MESSAGE_LIST, -1); 155 if (accountId >= 0) { 156 // Show the message list for the new account 157 MessageList.actionHandleAccount(this, accountId, Mailbox.TYPE_INBOX); 158 finish(); 159 return; 160 } 161 } 162 163 setContentView(R.layout.account_setup_basics); 164 165 mEmailView = (EditText)findViewById(R.id.account_email); 166 mPasswordView = (EditText)findViewById(R.id.account_password); 167 mDefaultView = (CheckBox)findViewById(R.id.account_default); 168 mNextButton = (Button)findViewById(R.id.next); 169 mManualSetupButton = (Button)findViewById(R.id.manual_setup); 170 171 mNextButton.setOnClickListener(this); 172 mManualSetupButton.setOnClickListener(this); 173 174 mEmailView.addTextChangedListener(this); 175 mPasswordView.addTextChangedListener(this); 176 177 // Find out how many accounts we have, and if there one or more, then we have a choice 178 // about being default or not. 179 Cursor c = null; 180 try { 181 c = getContentResolver().query( 182 EmailContent.Account.CONTENT_URI, 183 EmailContent.Account.ID_PROJECTION, 184 null, null, null); 185 if (c.getCount() > 0) { 186 mDefaultView.setVisibility(View.VISIBLE); 187 } 188 } finally { 189 if (c != null) { 190 c.close(); 191 } 192 } 193 194 mEasFlowMode = intent.getBooleanExtra(EXTRA_EAS_FLOW, false); 195 if (mEasFlowMode) { 196 // No need for manual button -> next is appropriate 197 mManualSetupButton.setVisibility(View.GONE); 198 // Swap welcome text for EAS-specific text 199 TextView welcomeView = (TextView) findViewById(R.id.instructions); 200 final boolean alternateStrings = 201 VendorPolicyLoader.getInstance(this).useAlternateExchangeStrings(); 202 setTitle(alternateStrings 203 ? R.string.account_setup_basics_exchange_title_alternate 204 : R.string.account_setup_basics_exchange_title); 205 welcomeView.setText(alternateStrings 206 ? R.string.accounts_welcome_exchange_alternate 207 : R.string.accounts_welcome_exchange); 208 } 209 210 if (intent.hasExtra(EXTRA_USERNAME)) { 211 mEmailView.setText(intent.getStringExtra(EXTRA_USERNAME)); 212 } 213 if (intent.hasExtra(EXTRA_PASSWORD)) { 214 mPasswordView.setText(intent.getStringExtra(EXTRA_PASSWORD)); 215 } 216 217 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 218 mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT); 219 } 220 221 if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) { 222 mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER); 223 } 224 } 225 226 @Override 227 public void onResume() { 228 super.onResume(); 229 validateFields(); 230 } 231 232 @Override 233 public void onSaveInstanceState(Bundle outState) { 234 super.onSaveInstanceState(outState); 235 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 236 if (mProvider != null) { 237 outState.putSerializable(STATE_KEY_PROVIDER, mProvider); 238 } 239 } 240 241 public void afterTextChanged(Editable s) { 242 validateFields(); 243 } 244 245 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 246 } 247 248 public void onTextChanged(CharSequence s, int start, int before, int count) { 249 } 250 251 private void validateFields() { 252 boolean valid = Utility.requiredFieldValid(mEmailView) 253 && Utility.requiredFieldValid(mPasswordView) 254 && mEmailValidator.isValid(mEmailView.getText().toString().trim()); 255 mNextButton.setEnabled(valid); 256 mManualSetupButton.setEnabled(valid); 257 /* 258 * Dim the next button's icon to 50% if the button is disabled. 259 * TODO this can probably be done with a stateful drawable. Check into it. 260 * android:state_enabled 261 */ 262 Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128); 263 } 264 265 private String getOwnerName() { 266 String name = null; 267/* TODO figure out another way to get the owner name 268 String projection[] = { 269 ContactMethods.NAME 270 }; 271 Cursor c = getContentResolver().query( 272 Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null, 273 null); 274 if (c != null) { 275 if (c.moveToFirst()) { 276 name = c.getString(0); 277 } 278 c.close(); 279 } 280*/ 281 282 if (name == null || name.length() == 0) { 283 long defaultId = Account.getDefaultAccountId(this); 284 if (defaultId != -1) { 285 Account account = Account.restoreAccountWithId(this, defaultId); 286 if (account != null) { 287 name = account.getSenderName(); 288 } 289 } 290 } 291 return name; 292 } 293 294 @Override 295 public Dialog onCreateDialog(int id) { 296 if (id == DIALOG_NOTE) { 297 if (mProvider != null && mProvider.note != null) { 298 return new AlertDialog.Builder(this) 299 .setIcon(android.R.drawable.ic_dialog_alert) 300 .setTitle(android.R.string.dialog_alert_title) 301 .setMessage(mProvider.note) 302 .setPositiveButton( 303 getString(R.string.okay_action), 304 new DialogInterface.OnClickListener() { 305 public void onClick(DialogInterface dialog, int which) { 306 finishAutoSetup(); 307 } 308 }) 309 .setNegativeButton( 310 getString(R.string.cancel_action), 311 null) 312 .create(); 313 } 314 } else if (id == DIALOG_DUPLICATE_ACCOUNT) { 315 return new AlertDialog.Builder(this) 316 .setIcon(android.R.drawable.ic_dialog_alert) 317 .setTitle(R.string.account_duplicate_dlg_title) 318 .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 319 mDuplicateAccountName)) 320 .setPositiveButton(R.string.okay_action, 321 new DialogInterface.OnClickListener() { 322 public void onClick(DialogInterface dialog, int which) { 323 dismissDialog(DIALOG_DUPLICATE_ACCOUNT); 324 } 325 }) 326 .create(); 327 } 328 return null; 329 } 330 331 /** 332 * Update a cached dialog with current values (e.g. account name) 333 */ 334 @Override 335 public void onPrepareDialog(int id, Dialog dialog) { 336 switch (id) { 337 case DIALOG_NOTE: 338 if (mProvider != null && mProvider.note != null) { 339 AlertDialog alert = (AlertDialog) dialog; 340 alert.setMessage(mProvider.note); 341 } 342 break; 343 case DIALOG_DUPLICATE_ACCOUNT: 344 if (mDuplicateAccountName != null) { 345 AlertDialog alert = (AlertDialog) dialog; 346 alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 347 mDuplicateAccountName)); 348 } 349 break; 350 } 351 } 352 353 private void finishAutoSetup() { 354 String email = mEmailView.getText().toString().trim(); 355 String password = mPasswordView.getText().toString().trim(); 356 String[] emailParts = email.split("@"); 357 String user = emailParts[0]; 358 String domain = emailParts[1]; 359 URI incomingUri = null; 360 URI outgoingUri = null; 361 try { 362 String incomingUsername = mProvider.incomingUsernameTemplate; 363 incomingUsername = incomingUsername.replaceAll("\\$email", email); 364 incomingUsername = incomingUsername.replaceAll("\\$user", user); 365 incomingUsername = incomingUsername.replaceAll("\\$domain", domain); 366 367 URI incomingUriTemplate = mProvider.incomingUriTemplate; 368 incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" 369 + password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), 370 incomingUriTemplate.getPath(), null, null); 371 372 String outgoingUsername = mProvider.outgoingUsernameTemplate; 373 outgoingUsername = outgoingUsername.replaceAll("\\$email", email); 374 outgoingUsername = outgoingUsername.replaceAll("\\$user", user); 375 outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain); 376 377 URI outgoingUriTemplate = mProvider.outgoingUriTemplate; 378 outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":" 379 + password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), 380 outgoingUriTemplate.getPath(), null, null); 381 382 // Stop here if the login credentials duplicate an existing account 383 mDuplicateAccountName = Utility.findDuplicateAccount(this, -1, 384 incomingUri.getHost(), incomingUsername); 385 if (mDuplicateAccountName != null) { 386 this.showDialog(DIALOG_DUPLICATE_ACCOUNT); 387 return; 388 } 389 390 } catch (URISyntaxException use) { 391 /* 392 * If there is some problem with the URI we give up and go on to 393 * manual setup. 394 */ 395 onManualSetup(); 396 return; 397 } 398 399 mAccount = new EmailContent.Account(); 400 mAccount.setSenderName(getOwnerName()); 401 mAccount.setEmailAddress(email); 402 mAccount.setStoreUri(this, incomingUri.toString()); 403 mAccount.setSenderUri(this, outgoingUri.toString()); 404/* TODO figure out the best way to implement this concept 405 mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); 406 mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); 407 mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox)); 408 mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 409*/ 410 if (incomingUri.toString().startsWith("imap")) { 411 // Delete policy must be set explicitly, because IMAP does not provide a UI selection 412 // for it. This logic needs to be followed in the auto setup flow as well. 413 mAccount.setDeletePolicy(EmailContent.Account.DELETE_POLICY_ON_DELETE); 414 } 415 mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); 416 AccountSetupCheckSettings.actionValidateSettings(this, mAccount, true, true); 417 } 418 419 private void onNext() { 420 // If this is EAS flow, don't try to find a provider for the domain! 421 if (!mEasFlowMode) { 422 String email = mEmailView.getText().toString().trim(); 423 String[] emailParts = email.split("@"); 424 String domain = emailParts[1].trim(); 425 mProvider = findProviderForDomain(domain); 426 if (mProvider != null) { 427 if (mProvider.note != null) { 428 showDialog(DIALOG_NOTE); 429 } else { 430 finishAutoSetup(); 431 } 432 return; 433 } 434 } 435 // Can't use auto setup 436 onManualSetup(); 437 } 438 439 /** 440 * This is used in automatic setup mode to jump directly down to the names screen. 441 * 442 * NOTE: With this organization, it is *not* possible to auto-create an exchange account, 443 * because certain necessary actions happen during AccountSetupOptions (which we are 444 * skipping here). 445 */ 446 @Override 447 public void onActivityResult(int requestCode, int resultCode, Intent data) { 448 if (resultCode == RESULT_OK) { 449 String email = mAccount.getEmailAddress(); 450 boolean isDefault = mDefaultView.isChecked(); 451 mAccount.setDisplayName(email); 452 mAccount.setDefaultAccount(isDefault); 453 // At this point we write the Account object to the DB for the first time. 454 // From now on we'll only pass the accountId around. 455 mAccount.save(this); 456 // Update the backup (side copy) of the accounts 457 AccountBackupRestore.backupAccounts(this); 458 Email.setServicesEnabled(this); 459 AccountSetupNames.actionSetNames(this, mAccount.mId, false); 460 finish(); 461 } 462 } 463 464 private void onManualSetup() { 465 String email = mEmailView.getText().toString().trim(); 466 String password = mPasswordView.getText().toString().trim(); 467 String[] emailParts = email.split("@"); 468 String user = emailParts[0].trim(); 469 String domain = emailParts[1].trim(); 470 471 // Alternate entry to the debug options screen (for devices without a physical keyboard: 472 // Username: d@d.d 473 // Password: debug 474 if (ENTER_DEBUG_SCREEN && "d@d.d".equals(email) && "debug".equals(password)) { 475 mEmailView.setText(""); 476 mPasswordView.setText(""); 477 startActivity(new Intent(this, Debug.class)); 478 return; 479 } 480 481 mAccount = new EmailContent.Account(); 482 mAccount.setSenderName(getOwnerName()); 483 mAccount.setEmailAddress(email); 484 try { 485 URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null); 486 mAccount.setStoreUri(this, uri.toString()); 487 mAccount.setSenderUri(this, uri.toString()); 488 } catch (URISyntaxException use) { 489 // If we can't set up the URL, don't continue - account setup pages will fail too 490 Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG) 491 .show(); 492 mAccount = null; 493 return; 494 } 495/* TODO figure out the best way to implement this concept 496 mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); 497 mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); 498 mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox)); 499 mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 500*/ 501 mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); 502 503 AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked(), 504 mEasFlowMode); 505 } 506 507 public void onClick(View v) { 508 switch (v.getId()) { 509 case R.id.next: 510 onNext(); 511 break; 512 case R.id.manual_setup: 513 onManualSetup(); 514 break; 515 } 516 } 517 518 /** 519 * Attempts to get the given attribute as a String resource first, and if it fails 520 * returns the attribute as a simple String value. 521 * @param xml 522 * @param name 523 * @return the requested resource 524 */ 525 private String getXmlAttribute(XmlResourceParser xml, String name) { 526 int resId = xml.getAttributeResourceValue(null, name, 0); 527 if (resId == 0) { 528 return xml.getAttributeValue(null, name); 529 } 530 else { 531 return getString(resId); 532 } 533 } 534 535 /** 536 * Search the list of known Email providers looking for one that matches the user's email 537 * domain. We check for vendor supplied values first, then we look in providers_product.xml 538 * first, finally by the entries in platform providers.xml. This provides a nominal override 539 * capability. 540 * 541 * A match is defined as any provider entry for which the "domain" attribute matches. 542 * 543 * @param domain The domain portion of the user's email address 544 * @return suitable Provider definition, or null if no match found 545 */ 546 private Provider findProviderForDomain(String domain) { 547 Provider p = VendorPolicyLoader.getInstance(this).findProviderForDomain(domain); 548 if (p == null) { 549 p = findProviderForDomain(domain, R.xml.providers_product); 550 } 551 if (p == null) { 552 p = findProviderForDomain(domain, R.xml.providers); 553 } 554 return p; 555 } 556 557 /** 558 * Search a single resource containing known Email provider definitions. 559 * 560 * @param domain The domain portion of the user's email address 561 * @param resourceId Id of the provider resource to scan 562 * @return suitable Provider definition, or null if no match found 563 */ 564 private Provider findProviderForDomain(String domain, int resourceId) { 565 try { 566 XmlResourceParser xml = getResources().getXml(resourceId); 567 int xmlEventType; 568 Provider provider = null; 569 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { 570 if (xmlEventType == XmlResourceParser.START_TAG 571 && "provider".equals(xml.getName()) 572 && domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) { 573 provider = new Provider(); 574 provider.id = getXmlAttribute(xml, "id"); 575 provider.label = getXmlAttribute(xml, "label"); 576 provider.domain = getXmlAttribute(xml, "domain"); 577 provider.note = getXmlAttribute(xml, "note"); 578 } 579 else if (xmlEventType == XmlResourceParser.START_TAG 580 && "incoming".equals(xml.getName()) 581 && provider != null) { 582 provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri")); 583 provider.incomingUsernameTemplate = getXmlAttribute(xml, "username"); 584 } 585 else if (xmlEventType == XmlResourceParser.START_TAG 586 && "outgoing".equals(xml.getName()) 587 && provider != null) { 588 provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri")); 589 provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username"); 590 } 591 else if (xmlEventType == XmlResourceParser.END_TAG 592 && "provider".equals(xml.getName()) 593 && provider != null) { 594 return provider; 595 } 596 } 597 } 598 catch (Exception e) { 599 Log.e(Email.LOG_TAG, "Error while trying to load provider settings.", e); 600 } 601 return null; 602 } 603 604 public static class Provider implements Serializable { 605 private static final long serialVersionUID = 8511656164616538989L; 606 607 public String id; 608 public String label; 609 public String domain; 610 public URI incomingUriTemplate; 611 public String incomingUsernameTemplate; 612 public URI outgoingUriTemplate; 613 public String outgoingUsernameTemplate; 614 public String note; 615 } 616} 617