AccountSetupExchangeFragment.java revision bc47398187c6ffd132435e51d8d61e6ec79a79db
1/* 2 * Copyright (C) 2010 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 android.app.Activity; 20import android.content.Context; 21import android.content.Intent; 22import android.net.Uri; 23import android.os.Bundle; 24import android.os.RemoteException; 25import android.text.Editable; 26import android.text.TextWatcher; 27import android.util.Log; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.widget.CheckBox; 32import android.widget.CompoundButton; 33import android.widget.CompoundButton.OnCheckedChangeListener; 34import android.widget.EditText; 35import android.widget.TextView; 36 37import com.android.email.Email; 38import com.android.email.R; 39import com.android.email.activity.UiUtilities; 40import com.android.email.provider.AccountBackupRestore; 41import com.android.email.service.EmailServiceUtils; 42import com.android.email.view.CertificateSelector; 43import com.android.email.view.CertificateSelector.HostCallback; 44import com.android.emailcommon.Device; 45import com.android.emailcommon.Logging; 46import com.android.emailcommon.provider.Account; 47import com.android.emailcommon.provider.HostAuth; 48import com.android.emailcommon.utility.CertificateRequestor; 49import com.android.emailcommon.utility.Utility; 50 51import java.io.IOException; 52 53/** 54 * Provides generic setup for Exchange accounts. 55 * 56 * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL 57 * (for editing existing accounts). 58 */ 59public class AccountSetupExchangeFragment extends AccountServerBaseFragment 60 implements OnCheckedChangeListener, HostCallback { 61 62 private static final int CERTIFICATE_REQUEST = 0; 63 private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential"; 64 private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded"; 65 66 private static final int PORT_SSL = 443; 67 private static final int PORT_NORMAL = 80; 68 69 private EditText mUsernameView; 70 private EditText mPasswordView; 71 private EditText mServerView; 72 private EditText mPortView; 73 private CheckBox mSslSecurityView; 74 private CheckBox mTrustCertificatesView; 75 private CertificateSelector mClientCertificateSelector; 76 77 // Support for lifecycle 78 private boolean mStarted; 79 /* package */ boolean mLoaded; 80 private String mCacheLoginCredential; 81 82 /** 83 * Called to do initial creation of a fragment. This is called after 84 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 85 */ 86 @Override 87 public void onCreate(Bundle savedInstanceState) { 88 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 89 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate"); 90 } 91 super.onCreate(savedInstanceState); 92 93 if (savedInstanceState != null) { 94 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 95 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 96 } 97 mBaseScheme = HostAuth.SCHEME_EAS; 98 } 99 100 @Override 101 public View onCreateView(LayoutInflater inflater, ViewGroup container, 102 Bundle savedInstanceState) { 103 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 104 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); 105 } 106 int layoutId = mSettingsMode 107 ? R.layout.account_settings_exchange_fragment 108 : R.layout.account_setup_exchange_fragment; 109 110 View view = inflater.inflate(layoutId, container, false); 111 final Context context = getActivity(); 112 113 mUsernameView = UiUtilities.getView(view, R.id.account_username); 114 mPasswordView = UiUtilities.getView(view, R.id.account_password); 115 mServerView = UiUtilities.getView(view, R.id.account_server); 116 mPortView = (EditText) UiUtilities.getView(view, R.id.account_port); 117 mSslSecurityView = UiUtilities.getView(view, R.id.account_ssl); 118 mSslSecurityView.setOnCheckedChangeListener(this); 119 mTrustCertificatesView = UiUtilities.getView(view, R.id.account_trust_certificates); 120 mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector); 121 122 // Calls validateFields() which enables or disables the Next button 123 // based on the fields' validity. 124 TextWatcher validationTextWatcher = new TextWatcher() { 125 @Override 126 public void afterTextChanged(Editable s) { 127 validateFields(); 128 } 129 130 @Override 131 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 132 @Override 133 public void onTextChanged(CharSequence s, int start, int before, int count) { } 134 }; 135 // We're editing an existing account; don't allow modification of the user name 136 if (mSettingsMode) { 137 makeTextViewUneditable(mUsernameView, 138 getString(R.string.account_setup_username_uneditable_error)); 139 } 140 mUsernameView.addTextChangedListener(validationTextWatcher); 141 mPasswordView.addTextChangedListener(validationTextWatcher); 142 mServerView.addTextChangedListener(validationTextWatcher); 143 mPortView.addTextChangedListener(validationTextWatcher); 144 145 EditText lastView = mServerView; 146 lastView.setOnEditorActionListener(mDismissImeOnDoneListener); 147 148 String deviceId = ""; 149 try { 150 deviceId = Device.getDeviceId(context); 151 } catch (IOException e) { 152 // Not required 153 } 154 ((TextView) UiUtilities.getView(view, R.id.device_id)).setText(deviceId); 155 156 // Additional setup only used while in "settings" mode 157 onCreateViewSettingsMode(view); 158 159 return view; 160 } 161 162 @Override 163 public void onActivityCreated(Bundle savedInstanceState) { 164 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 165 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 166 } 167 super.onActivityCreated(savedInstanceState); 168 mClientCertificateSelector.setHostActivity(this); 169 } 170 171 /** 172 * Called when the Fragment is visible to the user. 173 */ 174 @Override 175 public void onStart() { 176 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 177 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStart"); 178 } 179 super.onStart(); 180 mStarted = true; 181 loadSettings(SetupData.getAccount()); 182 } 183 184 /** 185 * Called when the fragment is visible to the user and actively running. 186 */ 187 @Override 188 public void onResume() { 189 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 190 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onResume"); 191 } 192 super.onResume(); 193 validateFields(); 194 } 195 196 @Override 197 public void onPause() { 198 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 199 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onPause"); 200 } 201 super.onPause(); 202 } 203 204 /** 205 * Called when the Fragment is no longer started. 206 */ 207 @Override 208 public void onStop() { 209 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 210 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStop"); 211 } 212 super.onStop(); 213 mStarted = false; 214 } 215 216 /** 217 * Called when the fragment is no longer in use. 218 */ 219 @Override 220 public void onDestroy() { 221 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 222 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onDestroy"); 223 } 224 super.onDestroy(); 225 } 226 227 @Override 228 public void onSaveInstanceState(Bundle outState) { 229 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 230 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState"); 231 } 232 super.onSaveInstanceState(outState); 233 234 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 235 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 236 } 237 238 /** 239 * Activity provides callbacks here. This also triggers loading and setting up the UX 240 */ 241 @Override 242 public void setCallback(Callback callback) { 243 super.setCallback(callback); 244 if (mStarted) { 245 loadSettings(SetupData.getAccount()); 246 } 247 } 248 249 /** 250 * Force the given account settings to be loaded using {@link #loadSettings(Account)}. 251 * 252 * @return true if the loaded values pass validation 253 */ 254 private boolean forceLoadSettings(Account account) { 255 mLoaded = false; 256 return loadSettings(account); 257 } 258 259 260 private int getPortFromSecurityType() { 261 boolean useSsl = mSslSecurityView.isChecked(); 262 int port = useSsl ? PORT_SSL : PORT_NORMAL; 263 return port; 264 } 265 266 private void updatePortFromSecurityType() { 267 int port = getPortFromSecurityType(); 268 mPortView.setText(Integer.toString(port)); 269 } 270 271 /** 272 * Load the given account settings into the UI and then ensure the settings are valid. 273 * As an optimization, if the settings have already been loaded, the UI will not be 274 * updated, but, the account fields will still be validated. 275 * 276 * @return true if the loaded values pass validation 277 */ 278 /*package*/ boolean loadSettings(Account account) { 279 if (mLoaded) return validateFields(); 280 281 HostAuth hostAuth = account.mHostAuthRecv; 282 283 String userName = hostAuth.mLogin; 284 if (userName != null) { 285 // Add a backslash to the start of the username, but only if the username has no 286 // backslash in it. 287 if (userName.indexOf('\\') < 0) { 288 userName = "\\" + userName; 289 } 290 mUsernameView.setText(userName); 291 } 292 293 if (hostAuth.mPassword != null) { 294 mPasswordView.setText(hostAuth.mPassword); 295 // Since username is uneditable, focus on the next editable field 296 if (mSettingsMode) { 297 mPasswordView.requestFocus(); 298 } 299 } 300 301 String protocol = hostAuth.mProtocol; 302 if (protocol == null || !protocol.startsWith("eas")) { 303 throw new Error("Unknown account type: " + protocol); 304 } 305 306 if (hostAuth.mAddress != null) { 307 mServerView.setText(hostAuth.mAddress); 308 } 309 310 boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); 311 boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL); 312 mSslSecurityView.setChecked(ssl); 313 mTrustCertificatesView.setChecked(trustCertificates); 314 if (hostAuth.mClientCertAlias != null) { 315 mClientCertificateSelector.setCertificate(hostAuth.mClientCertAlias); 316 } 317 onUseSslChanged(ssl); 318 319 int port = hostAuth.mPort; 320 if (port != HostAuth.PORT_UNKNOWN) { 321 mPortView.setText(Integer.toString(port)); 322 } else { 323 updatePortFromSecurityType(); 324 } 325 mLoadedRecvAuth = hostAuth; 326 mLoaded = true; 327 return validateFields(); 328 } 329 330 private boolean usernameFieldValid(EditText usernameView) { 331 return Utility.isTextViewNotEmpty(usernameView) && 332 !usernameView.getText().toString().equals("\\"); 333 } 334 335 /** 336 * Check the values in the fields and decide if it makes sense to enable the "next" button 337 * @return true if all fields are valid, false if any fields are incomplete 338 */ 339 private boolean validateFields() { 340 if (!mLoaded) return false; 341 boolean enabled = usernameFieldValid(mUsernameView) 342 && Utility.isTextViewNotEmpty(mPasswordView) 343 && Utility.isServerNameValid(mServerView); 344 enableNextButton(enabled); 345 346 // Warn (but don't prevent) if password has leading/trailing spaces 347 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 348 349 return enabled; 350 } 351 352 @Override 353 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 354 if (buttonView.getId() == R.id.account_ssl) { 355 onUseSslChanged(isChecked); 356 } 357 } 358 359 public void onUseSslChanged(boolean useSsl) { 360 int mode = useSsl ? View.VISIBLE : View.GONE; 361 mTrustCertificatesView.setVisibility(mode); 362 UiUtilities.setVisibilitySafe(getView(), R.id.account_trust_certificates_divider, mode); 363 mClientCertificateSelector.setVisibility(mode); 364 UiUtilities.setVisibilitySafe(getView(), R.id.client_certificate_divider, mode); 365 } 366 367 @Override 368 public void onCheckSettingsComplete(final int result) { 369 if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED) { 370 mSslSecurityView.setChecked(true); 371 onCertificateRequested(); 372 return; 373 } 374 super.onCheckSettingsComplete(result); 375 } 376 377 378 /** 379 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 380 * Blocking - do not call from UI Thread. 381 */ 382 @Override 383 public void saveSettingsAfterEdit() { 384 Account account = SetupData.getAccount(); 385 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 386 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 387 // For EAS, notify ExchangeService that the password has changed 388 try { 389 EmailServiceUtils.getExchangeService(mContext, null).hostChanged(account.mId); 390 } catch (RemoteException e) { 391 // Nothing to be done if this fails 392 } 393 // Update the backup (side copy) of the accounts 394 AccountBackupRestore.backup(mContext); 395 } 396 397 /** 398 * Entry point from Activity after entering new settings and verifying them. For setup mode. 399 */ 400 @Override 401 public void saveSettingsAfterSetup() { 402 } 403 404 /** 405 * Entry point from Activity after entering new settings and verifying them. For setup mode. 406 */ 407 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 408 Account account = SetupData.getAccount(); 409 account.mHostAuthSend = newHostAuth; 410 account.mHostAuthRecv = newHostAuth; 411 // Auto discovery may have changed the auth settings; force load them 412 return forceLoadSettings(account); 413 } 414 415 /** 416 * Implements AccountCheckSettingsFragment.Callbacks 417 */ 418 @Override 419 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 420 AccountSetupExchange activity = (AccountSetupExchange) getActivity(); 421 activity.onAutoDiscoverComplete(result, hostAuth); 422 } 423 424 /** 425 * Entry point from Activity, when "next" button is clicked 426 */ 427 @Override 428 public void onNext() { 429 Account account = SetupData.getAccount(); 430 431 String userName = mUsernameView.getText().toString().trim(); 432 if (userName.startsWith("\\")) { 433 userName = userName.substring(1); 434 } 435 mCacheLoginCredential = userName; 436 String userPassword = mPasswordView.getText().toString(); 437 438 int flags = 0; 439 if (mSslSecurityView.isChecked()) { 440 flags |= HostAuth.FLAG_SSL; 441 } 442 if (mTrustCertificatesView.isChecked()) { 443 flags |= HostAuth.FLAG_TRUST_ALL; 444 } 445 String certAlias = mClientCertificateSelector.getCertificate(); 446 String serverAddress = mServerView.getText().toString().trim(); 447 448 String portText = mPortView.getText().toString().trim(); 449 int port; 450 try { 451 port = Integer.parseInt(portText); 452 } catch (NumberFormatException e) { 453 // Worst case, do something sensible 454 port = mSslSecurityView.isChecked() ? 443 : 80; 455 Log.d(Logging.LOG_TAG, "Non-integer server port; using '" + port + "'"); 456 } 457 458 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 459 sendAuth.setLogin(userName, userPassword); 460 sendAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias); 461 sendAuth.mDomain = null; 462 463 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 464 recvAuth.setLogin(userName, userPassword); 465 recvAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias); 466 recvAuth.mDomain = null; 467 468 // Check for a duplicate account (requires async DB work) and if OK, proceed with check 469 startDuplicateTaskCheck(account.mId, serverAddress, mCacheLoginCredential, 470 SetupData.CHECK_INCOMING); 471 } 472 473 @Override 474 public void onCertificateRequested() { 475 Intent intent = new Intent(CertificateRequestor.ACTION_REQUEST_CERT); 476 intent.setData(Uri.parse("eas://com.android.emailcommon/certrequest")); 477 startActivityForResult(intent, CERTIFICATE_REQUEST); 478 } 479 480 @Override 481 public void onActivityResult(int requestCode, int resultCode, Intent data) { 482 if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) { 483 String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS); 484 if (certAlias != null) { 485 mClientCertificateSelector.setCertificate(certAlias); 486 } 487 } 488 } 489 490} 491