AccountSetupExchangeFragment.java revision 31d9acbf0623872f9d4a2b3210b5970854b654c7
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.emailcommon.Logging; 24import com.android.emailcommon.provider.EmailContent.Account; 25import com.android.emailcommon.provider.EmailContent.HostAuth; 26import com.android.emailcommon.utility.Utility; 27import com.android.exchange.ExchangeService; 28 29import android.app.Activity; 30import android.content.Context; 31import android.os.Bundle; 32import android.os.RemoteException; 33import android.text.Editable; 34import android.text.TextWatcher; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.widget.CheckBox; 40import android.widget.CompoundButton; 41import android.widget.CompoundButton.OnCheckedChangeListener; 42import android.widget.EditText; 43import android.widget.TextView; 44 45import java.io.IOException; 46import java.net.URI; 47import java.net.URISyntaxException; 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 private View mTrustCertificatesDivider; 67 68 // Support for lifecycle 69 private boolean mStarted; 70 /* package */ boolean mLoaded; 71 private String mCacheLoginCredential; 72 73 /** 74 * Called to do initial creation of a fragment. This is called after 75 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 76 */ 77 @Override 78 public void onCreate(Bundle savedInstanceState) { 79 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 80 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate"); 81 } 82 super.onCreate(savedInstanceState); 83 84 if (savedInstanceState != null) { 85 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 86 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 87 } 88 } 89 90 @Override 91 public View onCreateView(LayoutInflater inflater, ViewGroup container, 92 Bundle savedInstanceState) { 93 if (Email.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) view.findViewById(R.id.account_username); 104 mPasswordView = (EditText) view.findViewById(R.id.account_password); 105 mServerView = (EditText) view.findViewById(R.id.account_server); 106 mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); 107 mSslSecurityView.setOnCheckedChangeListener(this); 108 mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); 109 mTrustCertificatesDivider = view.findViewById(R.id.account_trust_certificates_divider); 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 mUsernameView.addTextChangedListener(validationTextWatcher); 122 mPasswordView.addTextChangedListener(validationTextWatcher); 123 mServerView.addTextChangedListener(validationTextWatcher); 124 125 //EXCHANGE-REMOVE-SECTION-START 126 // Show device ID 127 try { 128 String deviceId = ExchangeService.getDeviceId(context); 129 ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); 130 } catch (IOException ignore) { 131 // There's nothing we can do here... 132 } 133 //EXCHANGE-REMOVE-SECTION-END 134 135 // Additional setup only used while in "settings" mode 136 onCreateViewSettingsMode(view); 137 138 return view; 139 } 140 141 @Override 142 public void onActivityCreated(Bundle savedInstanceState) { 143 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 144 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 145 } 146 super.onActivityCreated(savedInstanceState); 147 } 148 149 /** 150 * Called when the Fragment is visible to the user. 151 */ 152 @Override 153 public void onStart() { 154 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 155 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStart"); 156 } 157 super.onStart(); 158 mStarted = true; 159 loadSettings(SetupData.getAccount()); 160 } 161 162 /** 163 * Called when the fragment is visible to the user and actively running. 164 */ 165 @Override 166 public void onResume() { 167 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 168 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onResume"); 169 } 170 super.onResume(); 171 validateFields(); 172 } 173 174 @Override 175 public void onPause() { 176 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 177 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onPause"); 178 } 179 super.onPause(); 180 } 181 182 /** 183 * Called when the Fragment is no longer started. 184 */ 185 @Override 186 public void onStop() { 187 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 188 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStop"); 189 } 190 super.onStop(); 191 mStarted = false; 192 } 193 194 /** 195 * Called when the fragment is no longer in use. 196 */ 197 @Override 198 public void onDestroy() { 199 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 200 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onDestroy"); 201 } 202 super.onDestroy(); 203 } 204 205 @Override 206 public void onSaveInstanceState(Bundle outState) { 207 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 208 Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState"); 209 } 210 super.onSaveInstanceState(outState); 211 212 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 213 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 214 } 215 216 /** 217 * Activity provides callbacks here. This also triggers loading and setting up the UX 218 */ 219 @Override 220 public void setCallback(Callback callback) { 221 super.setCallback(callback); 222 if (mStarted) { 223 loadSettings(SetupData.getAccount()); 224 } 225 } 226 227 /** 228 * Load the current settings into the UI 229 * 230 * @return true if the loaded values pass validation 231 */ 232 /* package */ boolean loadSettings(Account account) { 233 if (mLoaded) return validateFields(); 234 235 HostAuth hostAuth = account.mHostAuthRecv; 236 237 String userName = hostAuth.mLogin; 238 if (userName != null) { 239 // Add a backslash to the start of the username, but only if the username has no 240 // backslash in it. 241 if (userName.indexOf('\\') < 0) { 242 userName = "\\" + userName; 243 } 244 mUsernameView.setText(userName); 245 } 246 247 if (hostAuth.mPassword != null) { 248 mPasswordView.setText(hostAuth.mPassword); 249 } 250 251 String protocol = hostAuth.mProtocol; 252 if (protocol == null || !protocol.startsWith("eas")) { 253 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); 254 } 255 256 if (hostAuth.mAddress != null) { 257 mServerView.setText(hostAuth.mAddress); 258 } 259 260 boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); 261 boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); 262 mSslSecurityView.setChecked(ssl); 263 mTrustCertificatesView.setChecked(trustCertificates); 264 showTrustCertificates(ssl); 265 266 try { 267 mLoadedUri = getUri(); 268 } catch (URISyntaxException ignore) { 269 // ignore; should not happen 270 } 271 272 mLoaded = true; 273 return validateFields(); 274 } 275 276 private boolean usernameFieldValid(EditText usernameView) { 277 return Utility.isTextViewNotEmpty(usernameView) && 278 !usernameView.getText().toString().equals("\\"); 279 } 280 281 /** 282 * Check the values in the fields and decide if it makes sense to enable the "next" button 283 * @return true if all fields are valid, false if any fields are incomplete 284 */ 285 private boolean validateFields() { 286 if (!mLoaded) return false; 287 boolean enabled = usernameFieldValid(mUsernameView) 288 && Utility.isTextViewNotEmpty(mPasswordView) 289 && Utility.isTextViewNotEmpty(mServerView); 290 if (enabled) { 291 try { 292 URI uri = getUri(); 293 } catch (URISyntaxException use) { 294 enabled = false; 295 } 296 } 297 enableNextButton(enabled); 298 299 // Warn (but don't prevent) if password has leading/trailing spaces 300 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 301 302 return enabled; 303 } 304 305 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 306 if (buttonView.getId() == R.id.account_ssl) { 307 showTrustCertificates(isChecked); 308 } 309 } 310 311 public void showTrustCertificates(boolean visible) { 312 int mode = visible ? View.VISIBLE : View.GONE; 313 mTrustCertificatesView.setVisibility(mode); 314 // Divider is optional (only on XL layouts) 315 if (mTrustCertificatesDivider != null) { 316 mTrustCertificatesDivider.setVisibility(mode); 317 } 318 } 319 320 /** 321 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 322 * Blocking - do not call from UI Thread. 323 */ 324 @Override 325 public void saveSettingsAfterEdit() { 326 Account account = SetupData.getAccount(); 327 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 328 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 329 // For EAS, notify ExchangeService that the password has changed 330 try { 331 ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId); 332 } catch (RemoteException e) { 333 // Nothing to be done if this fails 334 } 335 // Update the backup (side copy) of the accounts 336 AccountBackupRestore.backupAccounts(mContext); 337 } 338 339 /** 340 * Entry point from Activity after entering new settings and verifying them. For setup mode. 341 */ 342 @Override 343 public void saveSettingsAfterSetup() { 344 } 345 346 /** 347 * Entry point from Activity after entering new settings and verifying them. For setup mode. 348 */ 349 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 350 Account account = SetupData.getAccount(); 351 account.mHostAuthSend = newHostAuth; 352 account.mHostAuthRecv = newHostAuth; 353 return loadSettings(account); 354 } 355 356 /** 357 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 358 * a problem with the user input. 359 * @return a URI built from the account setup fields 360 */ 361 @Override 362 protected URI getUri() throws URISyntaxException { 363 boolean sslRequired = mSslSecurityView.isChecked(); 364 boolean trustCertificates = mTrustCertificatesView.isChecked(); 365 String scheme = (sslRequired) 366 ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") 367 : "eas"; 368 String userName = mUsernameView.getText().toString().trim(); 369 // Remove a leading backslash, if there is one, since we now automatically put one at 370 // the start of the username field 371 if (userName.startsWith("\\")) { 372 userName = userName.substring(1); 373 } 374 mCacheLoginCredential = userName; 375 String userInfo = userName + ":" + mPasswordView.getText(); 376 String host = mServerView.getText().toString().trim(); 377 String path = null; 378 379 URI uri = new URI( 380 scheme, 381 userInfo, 382 host, 383 0, 384 path, 385 null, 386 null); 387 388 return uri; 389 } 390 391 /** 392 * Implements AccountCheckSettingsFragment.Callbacks 393 */ 394 @Override 395 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 396 AccountSetupExchange activity = (AccountSetupExchange) getActivity(); 397 activity.onAutoDiscoverComplete(result, hostAuth); 398 } 399 400 /** 401 * Entry point from Activity, when "next" button is clicked 402 */ 403 @Override 404 public void onNext() { 405 try { 406 URI uri = getUri(); 407 Account setupAccount = SetupData.getAccount(); 408 setupAccount.setStoreUri(mContext, uri.toString()); 409 setupAccount.setSenderUri(mContext, uri.toString()); 410 411 // Check for a duplicate account (requires async DB work) and if OK, proceed with check 412 startDuplicateTaskCheck(setupAccount.mId, uri.getHost(), mCacheLoginCredential, 413 SetupData.CHECK_INCOMING); 414 } catch (URISyntaxException use) { 415 /* 416 * It's unrecoverable if we cannot create a URI from components that 417 * we validated to be safe. 418 */ 419 throw new Error(use); 420 } 421 } 422} 423