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