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