AccountSetupExchangeFragment.java revision ce4cce05b2ee5ea2d9629c189a79f7f30778f534
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.email.view.CertificateSelector; 26import com.android.emailcommon.Device; 27import com.android.emailcommon.Logging; 28import com.android.emailcommon.provider.Account; 29import com.android.emailcommon.provider.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; 49 50/** 51 * Provides generic setup for Exchange accounts. 52 * 53 * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL 54 * (for editing existing accounts). 55 */ 56public class AccountSetupExchangeFragment extends AccountServerBaseFragment 57 implements OnCheckedChangeListener { 58 59 private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential"; 60 private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded"; 61 62 private EditText mUsernameView; 63 private EditText mPasswordView; 64 private EditText mServerView; 65 private CheckBox mSslSecurityView; 66 private CheckBox mTrustCertificatesView; 67 private CertificateSelector mClientCertificateSelector; 68 69 // Support for lifecycle 70 private boolean mStarted; 71 /* package */ boolean mLoaded; 72 private String mCacheLoginCredential; 73 74 /** 75 * Called to do initial creation of a fragment. This is called after 76 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 77 */ 78 @Override 79 public void onCreate(Bundle savedInstanceState) { 80 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 81 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate"); 82 } 83 super.onCreate(savedInstanceState); 84 85 if (savedInstanceState != null) { 86 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 87 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 88 } 89 mBaseScheme = Store.STORE_SCHEME_EAS; 90 } 91 92 @Override 93 public View onCreateView(LayoutInflater inflater, ViewGroup container, 94 Bundle savedInstanceState) { 95 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 96 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); 97 } 98 int layoutId = mSettingsMode 99 ? R.layout.account_settings_exchange_fragment 100 : R.layout.account_setup_exchange_fragment; 101 102 View view = inflater.inflate(layoutId, container, false); 103 Context context = getActivity(); 104 105 mUsernameView = UiUtilities.getView(view, R.id.account_username); 106 mPasswordView = UiUtilities.getView(view, R.id.account_password); 107 mServerView = UiUtilities.getView(view, R.id.account_server); 108 mSslSecurityView = UiUtilities.getView(view, R.id.account_ssl); 109 mSslSecurityView.setOnCheckedChangeListener(this); 110 mTrustCertificatesView = UiUtilities.getView(view, R.id.account_trust_certificates); 111 mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector); 112 113 // Calls validateFields() which enables or disables the Next button 114 // based on the fields' validity. 115 TextWatcher validationTextWatcher = new TextWatcher() { 116 public void afterTextChanged(Editable s) { 117 validateFields(); 118 } 119 120 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 121 public void onTextChanged(CharSequence s, int start, int before, int count) { } 122 }; 123 // We're editing an existing account; don't allow modification of the user name 124 if (mSettingsMode) { 125 makeTextViewUneditable(mUsernameView, 126 getString(R.string.account_setup_username_uneditable_error)); 127 } 128 mUsernameView.addTextChangedListener(validationTextWatcher); 129 mPasswordView.addTextChangedListener(validationTextWatcher); 130 mServerView.addTextChangedListener(validationTextWatcher); 131 String deviceId = ""; 132 try { 133 deviceId = Device.getDeviceId(context); 134 } catch (IOException e) { 135 // Not required 136 } 137 ((TextView) UiUtilities.getView(view, R.id.device_id)).setText(deviceId); 138 139 // Additional setup only used while in "settings" mode 140 onCreateViewSettingsMode(view); 141 142 return view; 143 } 144 145 @Override 146 public void onActivityCreated(Bundle savedInstanceState) { 147 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 148 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 149 } 150 super.onActivityCreated(savedInstanceState); 151 mClientCertificateSelector.setActivity(getActivity()); 152 } 153 154 /** 155 * Called when the Fragment is visible to the user. 156 */ 157 @Override 158 public void onStart() { 159 if (Logging.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 (Logging.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 (Logging.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 (Logging.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 (Logging.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 (Logging.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: " + protocol); 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 if (hostAuth.mClientCertAlias != null) { 282 mClientCertificateSelector.setCertificate(hostAuth.mClientCertAlias); 283 } 284 onUseSslChanged(ssl); 285 286 mLoadedRecvAuth = hostAuth; 287 mLoaded = true; 288 return validateFields(); 289 } 290 291 private boolean usernameFieldValid(EditText usernameView) { 292 return Utility.isTextViewNotEmpty(usernameView) && 293 !usernameView.getText().toString().equals("\\"); 294 } 295 296 /** 297 * Check the values in the fields and decide if it makes sense to enable the "next" button 298 * @return true if all fields are valid, false if any fields are incomplete 299 */ 300 private boolean validateFields() { 301 if (!mLoaded) return false; 302 boolean enabled = usernameFieldValid(mUsernameView) 303 && Utility.isTextViewNotEmpty(mPasswordView) 304 && Utility.isServerNameValid(mServerView); 305 enableNextButton(enabled); 306 307 // Warn (but don't prevent) if password has leading/trailing spaces 308 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 309 310 return enabled; 311 } 312 313 @Override 314 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 315 if (buttonView.getId() == R.id.account_ssl) { 316 onUseSslChanged(isChecked); 317 } 318 } 319 320 public void onUseSslChanged(boolean useSsl) { 321 int mode = useSsl ? View.VISIBLE : View.GONE; 322 mTrustCertificatesView.setVisibility(mode); 323 UiUtilities.setVisibilitySafe(getView(), R.id.account_trust_certificates_divider, mode); 324 mClientCertificateSelector.setVisibility(mode); 325 UiUtilities.setVisibilitySafe(getView(), R.id.client_certificate_divider, mode); 326 } 327 328 /** 329 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 330 * Blocking - do not call from UI Thread. 331 */ 332 @Override 333 public void saveSettingsAfterEdit() { 334 Account account = SetupData.getAccount(); 335 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 336 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 337 // For EAS, notify ExchangeService that the password has changed 338 try { 339 ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId); 340 } catch (RemoteException e) { 341 // Nothing to be done if this fails 342 } 343 // Update the backup (side copy) of the accounts 344 AccountBackupRestore.backup(mContext); 345 } 346 347 /** 348 * Entry point from Activity after entering new settings and verifying them. For setup mode. 349 */ 350 @Override 351 public void saveSettingsAfterSetup() { 352 } 353 354 /** 355 * Entry point from Activity after entering new settings and verifying them. For setup mode. 356 */ 357 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 358 Account account = SetupData.getAccount(); 359 account.mHostAuthSend = newHostAuth; 360 account.mHostAuthRecv = newHostAuth; 361 // Auto discovery may have changed the auth settings; force load them 362 return forceLoadSettings(account); 363 } 364 365 /** 366 * Implements AccountCheckSettingsFragment.Callbacks 367 */ 368 @Override 369 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 370 AccountSetupExchange activity = (AccountSetupExchange) getActivity(); 371 activity.onAutoDiscoverComplete(result, hostAuth); 372 } 373 374 /** 375 * Entry point from Activity, when "next" button is clicked 376 */ 377 @Override 378 public void onNext() { 379 Account account = SetupData.getAccount(); 380 381 String userName = mUsernameView.getText().toString().trim(); 382 if (userName.startsWith("\\")) { 383 userName = userName.substring(1); 384 } 385 mCacheLoginCredential = userName; 386 String userPassword = mPasswordView.getText().toString(); 387 388 int flags = 0; 389 if (mSslSecurityView.isChecked()) { 390 flags |= HostAuth.FLAG_SSL; 391 } 392 if (mTrustCertificatesView.isChecked()) { 393 flags |= HostAuth.FLAG_TRUST_ALL; 394 } 395 String certAlias = mClientCertificateSelector.getCertificate(); 396 String serverAddress = mServerView.getText().toString().trim(); 397 398 int port = mSslSecurityView.isChecked() ? 443 : 80; 399 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 400 sendAuth.setLogin(userName, userPassword); 401 sendAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias); 402 sendAuth.mDomain = null; 403 404 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 405 recvAuth.setLogin(userName, userPassword); 406 recvAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias); 407 recvAuth.mDomain = null; 408 409 // Check for a duplicate account (requires async DB work) and if OK, proceed with check 410 startDuplicateTaskCheck(account.mId, serverAddress, mCacheLoginCredential, 411 SetupData.CHECK_INCOMING); 412 } 413 414} 415