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