AccountSetupExchangeFragment.java revision 206109cf44e27e90e4a5208daa289704aa451198
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.AccountBackupRestore; 20import com.android.email.Email; 21import com.android.email.ExchangeUtils; 22import com.android.email.R; 23import com.android.email.mail.Store; 24import com.android.emailcommon.Device; 25import com.android.emailcommon.Logging; 26import com.android.emailcommon.provider.EmailContent; 27import com.android.emailcommon.provider.EmailContent.Account; 28import com.android.emailcommon.provider.EmailContent.HostAuth; 29import com.android.emailcommon.utility.Utility; 30 31import android.app.Activity; 32import android.content.Context; 33import android.os.Bundle; 34import android.os.RemoteException; 35import android.text.Editable; 36import android.text.TextWatcher; 37import android.util.Log; 38import android.view.LayoutInflater; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.CheckBox; 42import android.widget.CompoundButton; 43import android.widget.CompoundButton.OnCheckedChangeListener; 44import android.widget.EditText; 45import android.widget.TextView; 46 47import java.io.IOException; 48import java.net.URI; 49import java.net.URISyntaxException; 50 51/** 52 * Provides generic setup for Exchange accounts. 53 * 54 * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL 55 * (for editing existing accounts). 56 */ 57public class AccountSetupExchangeFragment extends AccountServerBaseFragment 58 implements OnCheckedChangeListener { 59 60 private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential"; 61 private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded"; 62 63 private EditText mUsernameView; 64 private EditText mPasswordView; 65 private EditText mServerView; 66 private CheckBox mSslSecurityView; 67 private CheckBox mTrustCertificatesView; 68 private View mTrustCertificatesDivider; 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) view.findViewById(R.id.account_username); 107 mPasswordView = (EditText) view.findViewById(R.id.account_password); 108 mServerView = (EditText) view.findViewById(R.id.account_server); 109 mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); 110 mSslSecurityView.setOnCheckedChangeListener(this); 111 mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); 112 mTrustCertificatesDivider = view.findViewById(R.id.account_trust_certificates_divider); 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 133 try { 134 String deviceId = Device.getDeviceId(context); 135 ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); 136 } catch (IOException e) { 137 // Not required 138 } 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 // Divider is optional (only on XL layouts) 327 if (mTrustCertificatesDivider != null) { 328 mTrustCertificatesDivider.setVisibility(mode); 329 } 330 } 331 332 /** 333 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 334 * Blocking - do not call from UI Thread. 335 */ 336 @Override 337 public void saveSettingsAfterEdit() { 338 Account account = SetupData.getAccount(); 339 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 340 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 341 // For EAS, notify ExchangeService that the password has changed 342 try { 343 ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId); 344 } catch (RemoteException e) { 345 // Nothing to be done if this fails 346 } 347 // Update the backup (side copy) of the accounts 348 AccountBackupRestore.backupAccounts(mContext); 349 } 350 351 /** 352 * Entry point from Activity after entering new settings and verifying them. For setup mode. 353 */ 354 @Override 355 public void saveSettingsAfterSetup() { 356 } 357 358 /** 359 * Entry point from Activity after entering new settings and verifying them. For setup mode. 360 */ 361 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 362 Account account = SetupData.getAccount(); 363 account.mHostAuthSend = newHostAuth; 364 account.mHostAuthRecv = newHostAuth; 365 // Auto discovery may have changed the auth settings; force load them 366 return forceLoadSettings(account); 367 } 368 369 /** 370 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 371 * a problem with the user input. 372 * @return a URI built from the account setup fields 373 */ 374 @Override 375 protected URI getUri() throws URISyntaxException { 376 Account account = SetupData.getAccount(); 377 boolean sslRequired = mSslSecurityView.isChecked(); 378 boolean trustCertificates = mTrustCertificatesView.isChecked(); 379 String userName = mUsernameView.getText().toString().trim(); 380 // Remove a leading backslash, if there is one, since we now automatically put one at 381 // the start of the username field 382 if (userName.startsWith("\\")) { 383 userName = userName.substring(1); 384 } 385 mCacheLoginCredential = userName; 386 String userInfo = userName + ":" + mPasswordView.getText(); 387 String host = mServerView.getText().toString().trim(); 388 String path = null; 389 int port = mSslSecurityView.isChecked() ? 443 : 80; 390 // Ensure TLS is not set 391 int flags = account.getOrCreateHostAuthRecv(mContext).mFlags & ~HostAuth.FLAG_TLS; 392 393 URI uri = new URI( 394 HostAuth.getSchemeString(mBaseScheme, flags), 395 userInfo, 396 host, 397 port, 398 path, 399 null, 400 null); 401 return uri; 402 } 403 404 /** 405 * Implements AccountCheckSettingsFragment.Callbacks 406 */ 407 @Override 408 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 409 AccountSetupExchange activity = (AccountSetupExchange) getActivity(); 410 activity.onAutoDiscoverComplete(result, hostAuth); 411 } 412 413 /** 414 * Entry point from Activity, when "next" button is clicked 415 */ 416 @Override 417 public void onNext() { 418 EmailContent.Account account = SetupData.getAccount(); 419 420 String userName = mUsernameView.getText().toString().trim(); 421 if (userName.startsWith("\\")) { 422 userName = userName.substring(1); 423 } 424 mCacheLoginCredential = userName; 425 String userPassword = mPasswordView.getText().toString(); 426 427 int flags = 0; 428 if (mSslSecurityView.isChecked()) { 429 flags |= HostAuth.FLAG_SSL; 430 } 431 if (mTrustCertificatesView.isChecked()) { 432 flags |= HostAuth.FLAG_TRUST_ALL; 433 } 434 String serverAddress = mServerView.getText().toString().trim(); 435 436 int port = mSslSecurityView.isChecked() ? 443 : 80; 437 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 438 sendAuth.setLogin(userName, userPassword); 439 sendAuth.setConnection(mBaseScheme, serverAddress, port, flags); 440 sendAuth.mDomain = null; 441 442 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 443 recvAuth.setLogin(userName, userPassword); 444 recvAuth.setConnection(mBaseScheme, serverAddress, port, flags); 445 recvAuth.mDomain = null; 446 447 // Check for a duplicate account (requires async DB work) and if OK, proceed with check 448 startDuplicateTaskCheck(account.mId, serverAddress, mCacheLoginCredential, 449 SetupData.CHECK_INCOMING); 450 } 451 452} 453