AccountSetupExchange.java revision 46199c65a357de63ae2899b90d5e0b70a1b7421f
1/* 2 * Copyright (C) 2009 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.AccountBackupRestore; 20import com.android.email.R; 21import com.android.email.Utility; 22import com.android.email.provider.EmailContent; 23import com.android.email.provider.EmailContent.Account; 24import com.android.email.provider.EmailContent.HostAuth; 25import com.android.email.service.EmailServiceProxy; 26import com.android.exchange.SyncManager; 27 28import android.app.Activity; 29import android.app.AlertDialog; 30import android.app.Dialog; 31import android.content.DialogInterface; 32import android.content.Intent; 33import android.os.Bundle; 34import android.os.Parcelable; 35import android.os.RemoteException; 36import android.text.Editable; 37import android.text.TextWatcher; 38import android.view.View; 39import android.view.View.OnClickListener; 40import android.widget.Button; 41import android.widget.CheckBox; 42import android.widget.CompoundButton; 43import android.widget.EditText; 44import android.widget.CompoundButton.OnCheckedChangeListener; 45 46import java.net.URI; 47import java.net.URISyntaxException; 48 49/** 50 * Provides generic setup for Exchange accounts. The following fields are supported: 51 * 52 * Email Address (from previous setup screen) 53 * Server 54 * Domain 55 * Requires SSL? 56 * User (login) 57 * Password 58 */ 59public class AccountSetupExchange extends Activity implements OnClickListener, 60 OnCheckedChangeListener { 61 /*package*/ static final String EXTRA_ACCOUNT = "account"; 62 private static final String EXTRA_MAKE_DEFAULT = "makeDefault"; 63 private static final String EXTRA_EAS_FLOW = "easFlow"; 64 /*package*/ static final String EXTRA_DISABLE_AUTO_DISCOVER = "disableAutoDiscover"; 65 66 private final static int DIALOG_DUPLICATE_ACCOUNT = 1; 67 68 private EditText mUsernameView; 69 private EditText mPasswordView; 70 private EditText mServerView; 71 private CheckBox mSslSecurityView; 72 private CheckBox mTrustCertificatesView; 73 74 private Button mNextButton; 75 private Account mAccount; 76 private boolean mMakeDefault; 77 private String mCacheLoginCredential; 78 private String mDuplicateAccountName; 79 80 public static void actionIncomingSettings(Activity fromActivity, Account account, 81 boolean makeDefault, boolean easFlowMode) { 82 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 83 i.putExtra(EXTRA_ACCOUNT, account); 84 i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault); 85 i.putExtra(EXTRA_EAS_FLOW, easFlowMode); 86 fromActivity.startActivity(i); 87 } 88 89 public static void actionEditIncomingSettings(Activity fromActivity, Account account) 90 { 91 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 92 i.setAction(Intent.ACTION_EDIT); 93 i.putExtra(EXTRA_ACCOUNT, account); 94 fromActivity.startActivity(i); 95 } 96 97 /** 98 * For now, we'll simply replicate outgoing, for the purpose of satisfying the 99 * account settings flow. 100 */ 101 public static void actionEditOutgoingSettings(Activity fromActivity, Account account) 102 { 103 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 104 i.setAction(Intent.ACTION_EDIT); 105 i.putExtra(EXTRA_ACCOUNT, account); 106 fromActivity.startActivity(i); 107 } 108 109 @Override 110 public void onCreate(Bundle savedInstanceState) { 111 super.onCreate(savedInstanceState); 112 setContentView(R.layout.account_setup_exchange); 113 114 mUsernameView = (EditText) findViewById(R.id.account_username); 115 mPasswordView = (EditText) findViewById(R.id.account_password); 116 mServerView = (EditText) findViewById(R.id.account_server); 117 mSslSecurityView = (CheckBox) findViewById(R.id.account_ssl); 118 mSslSecurityView.setOnCheckedChangeListener(this); 119 mTrustCertificatesView = (CheckBox) findViewById(R.id.account_trust_certificates); 120 121 mNextButton = (Button)findViewById(R.id.next); 122 mNextButton.setOnClickListener(this); 123 124 /* 125 * Calls validateFields() which enables or disables the Next button 126 * based on the fields' validity. 127 */ 128 TextWatcher validationTextWatcher = new TextWatcher() { 129 public void afterTextChanged(Editable s) { 130 validateFields(); 131 } 132 133 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 134 } 135 136 public void onTextChanged(CharSequence s, int start, int before, int count) { 137 } 138 }; 139 mUsernameView.addTextChangedListener(validationTextWatcher); 140 mPasswordView.addTextChangedListener(validationTextWatcher); 141 mServerView.addTextChangedListener(validationTextWatcher); 142 143 Intent intent = getIntent(); 144 mAccount = (EmailContent.Account) intent.getParcelableExtra(EXTRA_ACCOUNT); 145 mMakeDefault = intent.getBooleanExtra(EXTRA_MAKE_DEFAULT, false); 146 147 /* 148 * If we're being reloaded we override the original account with the one 149 * we saved 150 */ 151 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 152 mAccount = (EmailContent.Account) savedInstanceState.getParcelable(EXTRA_ACCOUNT); 153 } 154 155 String username = null; 156 String password = null; 157 158 try { 159 URI uri = new URI(mAccount.getStoreUri(this)); 160 if (uri.getUserInfo() != null) { 161 String[] userInfoParts = uri.getUserInfo().split(":", 2); 162 username = userInfoParts[0]; 163 if (userInfoParts.length > 1) { 164 password = userInfoParts[1]; 165 } 166 } 167 168 if (username != null) { 169 // Add a backslash to the start of the username, but only if the username has no 170 // backslash in it. 171 if (username.indexOf('\\') < 0) { 172 username = "\\" + username; 173 } 174 mUsernameView.setText(username); 175 } 176 177 if (password != null) { 178 mPasswordView.setText(password); 179 } 180 181 if (uri.getScheme().startsWith("eas")) { 182 // any other setup from mAccount can go here 183 } else { 184 throw new Error("Unknown account type: " + mAccount.getStoreUri(this)); 185 } 186 187 if (uri.getHost() != null) { 188 mServerView.setText(uri.getHost()); 189 } 190 191 boolean ssl = uri.getScheme().contains("ssl"); 192 mSslSecurityView.setChecked(ssl); 193 mTrustCertificatesView.setChecked(uri.getScheme().contains("trustallcerts")); 194 mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE); 195 196 } catch (URISyntaxException use) { 197 /* 198 * We should always be able to parse our own settings. 199 */ 200 throw new Error(use); 201 } 202 203 validateFields(); 204 205 // If we've got a username and password and we're NOT editing, try autodiscover 206 if (username != null && password != null && 207 !Intent.ACTION_EDIT.equals(intent.getAction())) { 208 // NOTE: Disabling AutoDiscover is only used in unit tests 209 boolean disableAutoDiscover = 210 intent.getBooleanExtra(EXTRA_DISABLE_AUTO_DISCOVER, false); 211 if (!disableAutoDiscover) { 212 AccountSetupCheckSettings 213 .actionAutoDiscover(this, mAccount, mAccount.mEmailAddress, password); 214 } 215 } 216 } 217 218 @Override 219 public void onSaveInstanceState(Bundle outState) { 220 super.onSaveInstanceState(outState); 221 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 222 } 223 224 private boolean usernameFieldValid(EditText usernameView) { 225 return Utility.requiredFieldValid(usernameView) && 226 !usernameView.getText().toString().equals("\\"); 227 } 228 229 /** 230 * Prepare a cached dialog with current values (e.g. account name) 231 */ 232 @Override 233 public Dialog onCreateDialog(int id) { 234 switch (id) { 235 case DIALOG_DUPLICATE_ACCOUNT: 236 return new AlertDialog.Builder(this) 237 .setIcon(android.R.drawable.ic_dialog_alert) 238 .setTitle(R.string.account_duplicate_dlg_title) 239 .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 240 mDuplicateAccountName)) 241 .setPositiveButton(R.string.okay_action, 242 new DialogInterface.OnClickListener() { 243 public void onClick(DialogInterface dialog, int which) { 244 dismissDialog(DIALOG_DUPLICATE_ACCOUNT); 245 } 246 }) 247 .create(); 248 } 249 return null; 250 } 251 252 /** 253 * Update a cached dialog with current values (e.g. account name) 254 */ 255 @Override 256 public void onPrepareDialog(int id, Dialog dialog) { 257 switch (id) { 258 case DIALOG_DUPLICATE_ACCOUNT: 259 if (mDuplicateAccountName != null) { 260 AlertDialog alert = (AlertDialog) dialog; 261 alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 262 mDuplicateAccountName)); 263 } 264 break; 265 } 266 } 267 268 /** 269 * Check the values in the fields and decide if it makes sense to enable the "next" button 270 * NOTE: Does it make sense to extract & combine with similar code in AccountSetupIncoming? 271 */ 272 private void validateFields() { 273 boolean enabled = usernameFieldValid(mUsernameView) 274 && Utility.requiredFieldValid(mPasswordView) 275 && Utility.requiredFieldValid(mServerView); 276 if (enabled) { 277 try { 278 URI uri = getUri(); 279 } catch (URISyntaxException use) { 280 enabled = false; 281 } 282 } 283 mNextButton.setEnabled(enabled); 284 Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128); 285 } 286 287 private void doOptions() { 288 boolean easFlowMode = getIntent().getBooleanExtra(EXTRA_EAS_FLOW, false); 289 AccountSetupOptions.actionOptions(this, mAccount, mMakeDefault, easFlowMode); 290 finish(); 291 } 292 293 /** 294 * We can get here two ways, either by validate returning or by autodiscover returning. 295 */ 296 @Override 297 public void onActivityResult(int requestCode, int resultCode, Intent data) { 298 if (requestCode == AccountSetupCheckSettings.REQUEST_CODE_VALIDATE) { 299 if (resultCode == RESULT_OK) { 300 if (Intent.ACTION_EDIT.equals(getIntent().getAction())) { 301 if (mAccount.isSaved()) { 302 // Account.update will NOT save the HostAuth's 303 mAccount.update(this, mAccount.toContentValues()); 304 mAccount.mHostAuthRecv.update(this, 305 mAccount.mHostAuthRecv.toContentValues()); 306 mAccount.mHostAuthSend.update(this, 307 mAccount.mHostAuthSend.toContentValues()); 308 if (mAccount.mHostAuthRecv.mProtocol.equals("eas")) { 309 // For EAS, notify SyncManager that the password has changed 310 try { 311 new EmailServiceProxy(this, SyncManager.class) 312 .hostChanged(mAccount.mId); 313 } catch (RemoteException e) { 314 // Nothing to be done if this fails 315 } 316 } 317 } else { 318 // Account.save will save the HostAuth's 319 mAccount.save(this); 320 } 321 // Update the backup (side copy) of the accounts 322 AccountBackupRestore.backupAccounts(this); 323 finish(); 324 } else { 325 // Go directly to end - there is no 2nd screen for incoming settings 326 doOptions(); 327 } 328 } 329 } else if (requestCode == AccountSetupCheckSettings.REQUEST_CODE_AUTO_DISCOVER) { 330 // The idea here is that it only matters if we've gotten a HostAuth back from the 331 // autodiscover service call. In all other cases, we can ignore the result 332 if (data != null) { 333 Parcelable p = data.getParcelableExtra("HostAuth"); 334 if (p != null) { 335 HostAuth hostAuth = (HostAuth)p; 336 mAccount.mHostAuthSend = hostAuth; 337 mAccount.mHostAuthRecv = hostAuth; 338 doOptions(); 339 } 340 // If we've got an auth failed, we need to go back to the basic screen 341 // Otherwise, we just continue on with the Exchange setup screen 342 } else if (resultCode == AccountSetupCheckSettings.RESULT_AUTO_DISCOVER_AUTH_FAILED) { 343 finish(); 344 } 345 } 346 } 347 348 /** 349 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 350 * a problem with the user input. 351 * @return a URI built from the account setup fields 352 */ 353 private URI getUri() throws URISyntaxException { 354 boolean sslRequired = mSslSecurityView.isChecked(); 355 boolean trustCertificates = mTrustCertificatesView.isChecked(); 356 String scheme = (sslRequired) 357 ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") 358 : "eas"; 359 String userName = mUsernameView.getText().toString().trim(); 360 // Remove a leading backslash, if there is one, since we now automatically put one at 361 // the start of the username field 362 if (userName.startsWith("\\")) { 363 userName = userName.substring(1); 364 } 365 mCacheLoginCredential = userName; 366 String userInfo = userName + ":" + mPasswordView.getText().toString().trim(); 367 String host = mServerView.getText().toString().trim(); 368 String path = null; 369 370 URI uri = new URI( 371 scheme, 372 userInfo, 373 host, 374 0, 375 path, 376 null, 377 null); 378 379 return uri; 380 } 381 382 /** 383 * Note, in EAS, store & sender are the same, so we always populate them together 384 */ 385 private void onNext() { 386 try { 387 URI uri = getUri(); 388 mAccount.setStoreUri(this, uri.toString()); 389 mAccount.setSenderUri(this, uri.toString()); 390 391 // Stop here if the login credentials duplicate an existing account 392 // (unless they duplicate the existing account, as they of course will) 393 mDuplicateAccountName = Utility.findDuplicateAccount(this, mAccount.mId, 394 uri.getHost(), mCacheLoginCredential); 395 if (mDuplicateAccountName != null) { 396 this.showDialog(DIALOG_DUPLICATE_ACCOUNT); 397 return; 398 } 399 } catch (URISyntaxException use) { 400 /* 401 * It's unrecoverable if we cannot create a URI from components that 402 * we validated to be safe. 403 */ 404 throw new Error(use); 405 } 406 407 AccountSetupCheckSettings.actionValidateSettings(this, mAccount, true, false); 408 } 409 410 public void onClick(View v) { 411 switch (v.getId()) { 412 case R.id.next: 413 onNext(); 414 break; 415 } 416 } 417 418 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 419 if (buttonView.getId() == R.id.account_ssl) { 420 mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE); 421 } 422 } 423} 424