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 17 package com.android.email.activity.setup; 18 19 import com.android.email.AccountBackupRestore; 20 import com.android.email.Email; 21 import com.android.email.EmailAddressValidator; 22 import com.android.email.R; 23 import com.android.email.Utility; 24 import com.android.email.VendorPolicyLoader; 25 import com.android.email.activity.Debug; 26 import com.android.email.activity.MessageList; 27 import com.android.email.activity.setup.AccountSettingsUtils.Provider; 28 import com.android.email.provider.EmailContent; 29 import com.android.email.provider.EmailContent.Account; 30 import com.android.email.provider.EmailContent.Mailbox; 31 32 import android.app.Activity; 33 import android.app.AlertDialog; 34 import android.app.Dialog; 35 import android.content.Context; 36 import android.content.DialogInterface; 37 import android.content.Intent; 38 import android.database.Cursor; 39 import android.os.Bundle; 40 import android.text.Editable; 41 import android.text.TextWatcher; 42 import android.view.View; 43 import android.view.View.OnClickListener; 44 import android.widget.Button; 45 import android.widget.CheckBox; 46 import android.widget.EditText; 47 import android.widget.TextView; 48 import android.widget.Toast; 49 50 import java.net.URI; 51 import 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 */ 61 public 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 final boolean alternateStrings = 199 VendorPolicyLoader.getInstance(this).useAlternateExchangeStrings(); 200 setTitle(alternateStrings 201 ? R.string.account_setup_basics_exchange_title_alternate 202 : R.string.account_setup_basics_exchange_title); 203 welcomeView.setText(alternateStrings 204 ? R.string.accounts_welcome_exchange_alternate 205 : R.string.accounts_welcome_exchange); 206 } 207 208 if (intent.hasExtra(EXTRA_USERNAME)) { 209 mEmailView.setText(intent.getStringExtra(EXTRA_USERNAME)); 210 } 211 if (intent.hasExtra(EXTRA_PASSWORD)) { 212 mPasswordView.setText(intent.getStringExtra(EXTRA_PASSWORD)); 213 } 214 215 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 216 mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT); 217 } 218 219 if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) { 220 mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER); 221 } 222 } 223 224 @Override 225 public void onResume() { 226 super.onResume(); 227 validateFields(); 228 } 229 230 @Override 231 public void onSaveInstanceState(Bundle outState) { 232 super.onSaveInstanceState(outState); 233 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 234 if (mProvider != null) { 235 outState.putSerializable(STATE_KEY_PROVIDER, mProvider); 236 } 237 } 238 239 public void afterTextChanged(Editable s) { 240 validateFields(); 241 } 242 243 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 244 } 245 246 public void onTextChanged(CharSequence s, int start, int before, int count) { 247 } 248 249 private void validateFields() { 250 boolean valid = Utility.requiredFieldValid(mEmailView) 251 && Utility.requiredFieldValid(mPasswordView) 252 && mEmailValidator.isValid(mEmailView.getText().toString().trim()); 253 mNextButton.setEnabled(valid); 254 mManualSetupButton.setEnabled(valid); 255 /* 256 * Dim the next button's icon to 50% if the button is disabled. 257 * TODO this can probably be done with a stateful drawable. Check into it. 258 * android:state_enabled 259 */ 260 Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128); 261 } 262 263 private String getOwnerName() { 264 String name = null; 265 /* TODO figure out another way to get the owner name 266 String projection[] = { 267 ContactMethods.NAME 268 }; 269 Cursor c = getContentResolver().query( 270 Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null, 271 null); 272 if (c != null) { 273 if (c.moveToFirst()) { 274 name = c.getString(0); 275 } 276 c.close(); 277 } 278 */ 279 280 if (name == null || name.length() == 0) { 281 long defaultId = Account.getDefaultAccountId(this); 282 if (defaultId != -1) { 283 Account account = Account.restoreAccountWithId(this, defaultId); 284 if (account != null) { 285 name = account.getSenderName(); 286 } 287 } 288 } 289 return name; 290 } 291 292 @Override 293 public Dialog onCreateDialog(int id) { 294 if (id == DIALOG_NOTE) { 295 if (mProvider != null && mProvider.note != null) { 296 return new AlertDialog.Builder(this) 297 .setIcon(android.R.drawable.ic_dialog_alert) 298 .setTitle(android.R.string.dialog_alert_title) 299 .setMessage(mProvider.note) 300 .setPositiveButton( 301 getString(R.string.okay_action), 302 new DialogInterface.OnClickListener() { 303 public void onClick(DialogInterface dialog, int which) { 304 finishAutoSetup(); 305 } 306 }) 307 .setNegativeButton( 308 getString(R.string.cancel_action), 309 null) 310 .create(); 311 } 312 } else if (id == DIALOG_DUPLICATE_ACCOUNT) { 313 return new AlertDialog.Builder(this) 314 .setIcon(android.R.drawable.ic_dialog_alert) 315 .setTitle(R.string.account_duplicate_dlg_title) 316 .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 317 mDuplicateAccountName)) 318 .setPositiveButton(R.string.okay_action, 319 new DialogInterface.OnClickListener() { 320 public void onClick(DialogInterface dialog, int which) { 321 dismissDialog(DIALOG_DUPLICATE_ACCOUNT); 322 } 323 }) 324 .create(); 325 } 326 return null; 327 } 328 329 /** 330 * Update a cached dialog with current values (e.g. account name) 331 */ 332 @Override 333 public void onPrepareDialog(int id, Dialog dialog) { 334 switch (id) { 335 case DIALOG_NOTE: 336 if (mProvider != null && mProvider.note != null) { 337 AlertDialog alert = (AlertDialog) dialog; 338 alert.setMessage(mProvider.note); 339 } 340 break; 341 case DIALOG_DUPLICATE_ACCOUNT: 342 if (mDuplicateAccountName != null) { 343 AlertDialog alert = (AlertDialog) dialog; 344 alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 345 mDuplicateAccountName)); 346 } 347 break; 348 } 349 } 350 351 private void finishAutoSetup() { 352 String email = mEmailView.getText().toString().trim(); 353 String password = mPasswordView.getText().toString(); 354 String[] emailParts = email.split("@"); 355 String user = emailParts[0]; 356 String domain = emailParts[1]; 357 URI incomingUri = null; 358 URI outgoingUri = null; 359 try { 360 String incomingUsername = mProvider.incomingUsernameTemplate; 361 incomingUsername = incomingUsername.replaceAll("\\$email", email); 362 incomingUsername = incomingUsername.replaceAll("\\$user", user); 363 incomingUsername = incomingUsername.replaceAll("\\$domain", domain); 364 365 URI incomingUriTemplate = mProvider.incomingUriTemplate; 366 incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" 367 + password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), 368 incomingUriTemplate.getPath(), null, null); 369 370 String outgoingUsername = mProvider.outgoingUsernameTemplate; 371 outgoingUsername = outgoingUsername.replaceAll("\\$email", email); 372 outgoingUsername = outgoingUsername.replaceAll("\\$user", user); 373 outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain); 374 375 URI outgoingUriTemplate = mProvider.outgoingUriTemplate; 376 outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":" 377 + password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), 378 outgoingUriTemplate.getPath(), null, null); 379 380 // Stop here if the login credentials duplicate an existing account 381 mDuplicateAccountName = Utility.findDuplicateAccount(this, -1, 382 incomingUri.getHost(), incomingUsername); 383 if (mDuplicateAccountName != null) { 384 this.showDialog(DIALOG_DUPLICATE_ACCOUNT); 385 return; 386 } 387 388 } catch (URISyntaxException use) { 389 /* 390 * If there is some problem with the URI we give up and go on to 391 * manual setup. Technically speaking, AutoDiscover is OK here, since user clicked 392 * "Next" to get here. This would never happen in practice because we don't expect 393 * to find any EAS accounts in the providers list. 394 */ 395 onManualSetup(true); 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 = AccountSettingsUtils.findProviderForDomain(this, 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 (although EAS accounts may still be able to AutoDiscover) 436 onManualSetup(true); 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 /** 465 * @param allowAutoDiscover - true if the user clicked 'next' and (if the account is EAS) 466 * it's OK to use autodiscover. false to prevent autodiscover and go straight to manual setup. 467 * Ignored for IMAP & POP accounts. 468 */ 469 private void onManualSetup(boolean allowAutoDiscover) { 470 String email = mEmailView.getText().toString().trim(); 471 String password = mPasswordView.getText().toString(); 472 String[] emailParts = email.split("@"); 473 String user = emailParts[0].trim(); 474 String domain = emailParts[1].trim(); 475 476 // Alternate entry to the debug options screen (for devices without a physical keyboard: 477 // Username: d (at) d.d 478 // Password: debug 479 if (ENTER_DEBUG_SCREEN && "d (at) d.d".equals(email) && "debug".equals(password)) { 480 mEmailView.setText(""); 481 mPasswordView.setText(""); 482 startActivity(new Intent(this, Debug.class)); 483 return; 484 } 485 486 mAccount = new EmailContent.Account(); 487 mAccount.setSenderName(getOwnerName()); 488 mAccount.setEmailAddress(email); 489 try { 490 URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null); 491 mAccount.setStoreUri(this, uri.toString()); 492 mAccount.setSenderUri(this, uri.toString()); 493 } catch (URISyntaxException use) { 494 // If we can't set up the URL, don't continue - account setup pages will fail too 495 Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG) 496 .show(); 497 mAccount = null; 498 return; 499 } 500 /* TODO figure out the best way to implement this concept 501 mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); 502 mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); 503 mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox)); 504 mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 505 */ 506 mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL); 507 508 AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked(), 509 mEasFlowMode, allowAutoDiscover); 510 } 511 512 public void onClick(View v) { 513 switch (v.getId()) { 514 case R.id.next: 515 onNext(); 516 break; 517 case R.id.manual_setup: 518 // no AutoDiscover - user clicked "manual" 519 onManualSetup(false); 520 break; 521 } 522 } 523 } 524