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