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