AccountSetupBasics.java revision a5af05f1a94ba9d0a91bd6507492cf6946f88a4d
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.Preferences; 22import com.android.email.R; 23import com.android.email.Utility; 24import com.android.email.activity.Debug; 25import com.android.email.provider.EmailContent; 26import com.android.email.provider.EmailContent.Account; 27import com.android.email.provider.EmailContent.AccountColumns; 28 29import android.app.Activity; 30import android.app.AlertDialog; 31import android.app.Dialog; 32import android.content.ContentValues; 33import android.content.DialogInterface; 34import android.content.Intent; 35import android.content.res.XmlResourceParser; 36import android.database.Cursor; 37import android.net.Uri; 38import android.os.Bundle; 39import android.provider.Contacts; 40import android.provider.Contacts.People.ContactMethods; 41import android.text.Editable; 42import android.text.TextWatcher; 43import android.util.Log; 44import android.view.View; 45import android.view.View.OnClickListener; 46import android.widget.Button; 47import android.widget.CheckBox; 48import android.widget.EditText; 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 private final static String EXTRA_ACCOUNT = "com.android.email.AccountSetupBasics.account"; 67 private final static int DIALOG_NOTE = 1; 68 private final static String STATE_KEY_PROVIDER = 69 "com.android.email.AccountSetupBasics.provider"; 70 71 // NOTE: If you change this value, confirm that the new interval exists in arrays.xml 72 private final static int DEFAULT_ACCOUNT_CHECK_INTERVAL = 15; 73 74 private Preferences mPrefs; 75 private EditText mEmailView; 76 private EditText mPasswordView; 77 private CheckBox mDefaultView; 78 private Button mNextButton; 79 private Button mManualSetupButton; 80 private EmailContent.Account mAccount; 81 private Provider mProvider; 82 83 private EmailAddressValidator mEmailValidator = new EmailAddressValidator(); 84 85 public static void actionNewAccount(Activity fromActivity) { 86 Intent i = new Intent(fromActivity, AccountSetupBasics.class); 87 fromActivity.startActivity(i); 88 } 89 90 @Override 91 public void onCreate(Bundle savedInstanceState) { 92 super.onCreate(savedInstanceState); 93 setContentView(R.layout.account_setup_basics); 94 mPrefs = Preferences.getPreferences(this); 95 mEmailView = (EditText)findViewById(R.id.account_email); 96 mPasswordView = (EditText)findViewById(R.id.account_password); 97 mDefaultView = (CheckBox)findViewById(R.id.account_default); 98 mNextButton = (Button)findViewById(R.id.next); 99 mManualSetupButton = (Button)findViewById(R.id.manual_setup); 100 101 mNextButton.setOnClickListener(this); 102 mManualSetupButton.setOnClickListener(this); 103 104 mEmailView.addTextChangedListener(this); 105 mPasswordView.addTextChangedListener(this); 106 107 // Find out how many accounts we have, and if there one or more, then we have a choice 108 // about being default or not. 109 Cursor c = null; 110 try { 111 c = getContentResolver().query( 112 EmailContent.Account.CONTENT_URI, 113 EmailContent.Account.ID_PROJECTION, 114 null, null, null); 115 if (c.getCount() > 0) { 116 mDefaultView.setVisibility(View.VISIBLE); 117 } 118 } finally { 119 if (c != null) { 120 c.close(); 121 } 122 } 123 124 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 125 mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT); 126 } 127 128 if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) { 129 mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER); 130 } 131 } 132 133 @Override 134 public void onResume() { 135 super.onResume(); 136 validateFields(); 137 } 138 139 @Override 140 public void onSaveInstanceState(Bundle outState) { 141 super.onSaveInstanceState(outState); 142 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 143 if (mProvider != null) { 144 outState.putSerializable(STATE_KEY_PROVIDER, mProvider); 145 } 146 } 147 148 public void afterTextChanged(Editable s) { 149 validateFields(); 150 } 151 152 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 153 } 154 155 public void onTextChanged(CharSequence s, int start, int before, int count) { 156 } 157 158 private void validateFields() { 159 boolean valid = Utility.requiredFieldValid(mEmailView) 160 && Utility.requiredFieldValid(mPasswordView) 161 && mEmailValidator.isValid(mEmailView.getText().toString().trim()); 162 mNextButton.setEnabled(valid); 163 mManualSetupButton.setEnabled(valid); 164 /* 165 * Dim the next button's icon to 50% if the button is disabled. 166 * TODO this can probably be done with a stateful drawable. Check into it. 167 * android:state_enabled 168 */ 169 Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128); 170 } 171 172 private String getOwnerName() { 173 String name = null; 174 String projection[] = { 175 ContactMethods.NAME 176 }; 177 Cursor c = getContentResolver().query( 178 Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null, 179 null); 180 if (c.getCount() > 0) { 181 c.moveToFirst(); 182 name = c.getString(0); 183 c.close(); 184 } 185 186 if (name == null || name.length() == 0) { 187 long defaultId = Account.getDefaultAccountId(this); 188 if (defaultId != -1) { 189 Account account = Account.restoreAccountWithId(this, defaultId); 190 if (account != null) { 191 name = account.getSenderName(); 192 } 193 } 194 } 195 return name; 196 } 197 198 @Override 199 public Dialog onCreateDialog(int id) { 200 if (id == DIALOG_NOTE) { 201 if (mProvider != null && mProvider.note != null) { 202 return new AlertDialog.Builder(this) 203 .setIcon(android.R.drawable.ic_dialog_alert) 204 .setTitle(android.R.string.dialog_alert_title) 205 .setMessage(mProvider.note) 206 .setPositiveButton( 207 getString(R.string.okay_action), 208 new DialogInterface.OnClickListener() { 209 public void onClick(DialogInterface dialog, int which) { 210 finishAutoSetup(); 211 } 212 }) 213 .setNegativeButton( 214 getString(R.string.cancel_action), 215 null) 216 .create(); 217 } 218 } 219 return null; 220 } 221 222 private void finishAutoSetup() { 223 String email = mEmailView.getText().toString().trim(); 224 String password = mPasswordView.getText().toString().trim(); 225 String[] emailParts = email.split("@"); 226 String user = emailParts[0]; 227 String domain = emailParts[1]; 228 URI incomingUri = null; 229 URI outgoingUri = null; 230 try { 231 String incomingUsername = mProvider.incomingUsernameTemplate; 232 incomingUsername = incomingUsername.replaceAll("\\$email", email); 233 incomingUsername = incomingUsername.replaceAll("\\$user", user); 234 incomingUsername = incomingUsername.replaceAll("\\$domain", domain); 235 236 URI incomingUriTemplate = mProvider.incomingUriTemplate; 237 incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" 238 + password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), 239 incomingUriTemplate.getPath(), null, null); 240 241 String outgoingUsername = mProvider.outgoingUsernameTemplate; 242 outgoingUsername = outgoingUsername.replaceAll("\\$email", email); 243 outgoingUsername = outgoingUsername.replaceAll("\\$user", user); 244 outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain); 245 246 URI outgoingUriTemplate = mProvider.outgoingUriTemplate; 247 outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":" 248 + password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), 249 outgoingUriTemplate.getPath(), null, null); 250 } catch (URISyntaxException use) { 251 /* 252 * If there is some problem with the URI we give up and go on to 253 * manual setup. 254 */ 255 onManualSetup(); 256 return; 257 } 258 259 mAccount = new EmailContent.Account(); 260 mAccount.setSenderName(getOwnerName()); 261 mAccount.setEmailAddress(email); 262 mAccount.setStoreUri(this, incomingUri.toString()); 263 mAccount.setSenderUri(this, outgoingUri.toString()); 264/* TODO figure out the best way to implement this concept 265 mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); 266 mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); 267 mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox)); 268 mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 269*/ 270 if (incomingUri.toString().startsWith("imap")) { 271 // Delete policy must be set explicitly, because IMAP does not provide a UI selection 272 // for it. This logic needs to be followed in the auto setup flow as well. 273 mAccount.setDeletePolicy(EmailContent.Account.DELETE_POLICY_ON_DELETE); 274 } 275 mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); 276 AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true); 277 } 278 279 private void onNext() { 280 String email = mEmailView.getText().toString().trim(); 281 String[] emailParts = email.split("@"); 282 String domain = emailParts[1].trim(); 283 mProvider = findProviderForDomain(domain); 284 if (mProvider == null) { 285 /* 286 * We don't have default settings for this account, start the manual 287 * setup process. 288 */ 289 onManualSetup(); 290 return; 291 } 292 293 if (mProvider.note != null) { 294 showDialog(DIALOG_NOTE); 295 } 296 else { 297 finishAutoSetup(); 298 } 299 } 300 301 @Override 302 public void onActivityResult(int requestCode, int resultCode, Intent data) { 303 if (resultCode == RESULT_OK) { 304 String email = mAccount.getEmailAddress(); 305 boolean isDefault = mDefaultView.isChecked(); 306 mAccount.setDisplayName(email); 307 mAccount.setDefaultAccount(isDefault); 308 // At this point we write the Account object to the DB for the first time. 309 // From now on we'll only pass the accountId around. 310 mAccount.save(this); 311 Email.setServicesEnabled(this); 312 AccountSetupNames.actionSetNames(this, mAccount.mId); 313 finish(); 314 } 315 } 316 317 private void onManualSetup() { 318 String email = mEmailView.getText().toString().trim(); 319 String password = mPasswordView.getText().toString().trim(); 320 String[] emailParts = email.split("@"); 321 String user = emailParts[0].trim(); 322 String domain = emailParts[1].trim(); 323 324 // Alternate entry to the debug options screen (for devices without a physical keyboard: 325 // Username: d@d.d 326 // Password: debug 327 if (ENTER_DEBUG_SCREEN && "d@d.d".equals(email) && "debug".equals(password)) { 328 startActivity(new Intent(this, Debug.class)); 329 return; 330 } 331 332 mAccount = new EmailContent.Account(); 333 mAccount.setSenderName(getOwnerName()); 334 mAccount.setEmailAddress(email); 335 try { 336 URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null); 337 mAccount.setStoreUri(this, uri.toString()); 338 mAccount.setSenderUri(this, uri.toString()); 339 } catch (URISyntaxException use) { 340 // If we can't set up the URL, don't continue - account setup pages will fail too 341 Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG) 342 .show(); 343 mAccount = null; 344 return; 345 } 346/* TODO figure out the best way to implement this concept 347 mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); 348 mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); 349 mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox)); 350 mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 351*/ 352 mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); 353 354 AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked()); 355 finish(); 356 } 357 358 public void onClick(View v) { 359 switch (v.getId()) { 360 case R.id.next: 361 onNext(); 362 break; 363 case R.id.manual_setup: 364 onManualSetup(); 365 break; 366 } 367 } 368 369 /** 370 * Attempts to get the given attribute as a String resource first, and if it fails 371 * returns the attribute as a simple String value. 372 * @param xml 373 * @param name 374 * @return the requested resource 375 */ 376 private String getXmlAttribute(XmlResourceParser xml, String name) { 377 int resId = xml.getAttributeResourceValue(null, name, 0); 378 if (resId == 0) { 379 return xml.getAttributeValue(null, name); 380 } 381 else { 382 return getString(resId); 383 } 384 } 385 386 /** 387 * Search the list of known Email providers looking for one that matches the user's email 388 * domain. We look in providers_product.xml first, followed by the entries in 389 * platform providers.xml. This provides a nominal override capability. 390 * 391 * A match is defined as any provider entry for which the "domain" attribute matches. 392 * 393 * @param domain The domain portion of the user's email address 394 * @return suitable Provider definition, or null if no match found 395 */ 396 private Provider findProviderForDomain(String domain) { 397 Provider p = findProviderForDomain(domain, R.xml.providers_product); 398 if (p == null) { 399 p = findProviderForDomain(domain, R.xml.providers); 400 } 401 return p; 402 } 403 404 /** 405 * Search a single resource containing known Email provider definitions. 406 * 407 * @param domain The domain portion of the user's email address 408 * @param resourceId Id of the provider resource to scan 409 * @return suitable Provider definition, or null if no match found 410 */ 411 private Provider findProviderForDomain(String domain, int resourceId) { 412 try { 413 XmlResourceParser xml = getResources().getXml(resourceId); 414 int xmlEventType; 415 Provider provider = null; 416 while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { 417 if (xmlEventType == XmlResourceParser.START_TAG 418 && "provider".equals(xml.getName()) 419 && domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) { 420 provider = new Provider(); 421 provider.id = getXmlAttribute(xml, "id"); 422 provider.label = getXmlAttribute(xml, "label"); 423 provider.domain = getXmlAttribute(xml, "domain"); 424 provider.note = getXmlAttribute(xml, "note"); 425 } 426 else if (xmlEventType == XmlResourceParser.START_TAG 427 && "incoming".equals(xml.getName()) 428 && provider != null) { 429 provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri")); 430 provider.incomingUsernameTemplate = getXmlAttribute(xml, "username"); 431 } 432 else if (xmlEventType == XmlResourceParser.START_TAG 433 && "outgoing".equals(xml.getName()) 434 && provider != null) { 435 provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri")); 436 provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username"); 437 } 438 else if (xmlEventType == XmlResourceParser.END_TAG 439 && "provider".equals(xml.getName()) 440 && provider != null) { 441 return provider; 442 } 443 } 444 } 445 catch (Exception e) { 446 Log.e(Email.LOG_TAG, "Error while trying to load provider settings.", e); 447 } 448 return null; 449 } 450 451 static class Provider implements Serializable { 452 private static final long serialVersionUID = 8511656164616538989L; 453 454 public String id; 455 456 public String label; 457 458 public String domain; 459 460 public URI incomingUriTemplate; 461 462 public String incomingUsernameTemplate; 463 464 public URI outgoingUriTemplate; 465 466 public String outgoingUsernameTemplate; 467 468 public String note; 469 } 470} 471