AccountSetupExchangeFragment.java revision 112ed496f817ebeab6b1ee1d5117259ef80342b2
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.Utility; 24import com.android.email.provider.EmailContent.Account; 25import com.android.email.provider.EmailContent.HostAuth; 26import com.android.exchange.ExchangeService; 27 28import android.app.Activity; 29import android.content.Context; 30import android.os.Bundle; 31import android.os.RemoteException; 32import android.text.Editable; 33import android.text.TextWatcher; 34import android.util.Log; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.ViewGroup; 38import android.widget.CheckBox; 39import android.widget.CompoundButton; 40import android.widget.CompoundButton.OnCheckedChangeListener; 41import android.widget.EditText; 42import android.widget.TextView; 43 44import java.io.IOException; 45import java.net.URI; 46import java.net.URISyntaxException; 47 48/** 49 * Provides generic setup for Exchange accounts. 50 * 51 * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL 52 * (for editing existing accounts). 53 */ 54public class AccountSetupExchangeFragment extends AccountServerBaseFragment 55 implements OnCheckedChangeListener { 56 57 private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential"; 58 private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded"; 59 60 private EditText mUsernameView; 61 private EditText mPasswordView; 62 private EditText mServerView; 63 private CheckBox mSslSecurityView; 64 private CheckBox mTrustCertificatesView; 65 private View mTrustCertificatesDivider; 66 67 // Support for lifecycle 68 private boolean mStarted; 69 private boolean mLoaded; 70 private String mCacheLoginCredential; 71 72 /** 73 * Create the fragment with parameters - used mainly to force into settings mode (with buttons) 74 * @param settingsMode if true, alters appearance for use in settings (default is "setup") 75 */ 76 public static AccountSetupExchangeFragment newInstance(boolean settingsMode) { 77 AccountSetupExchangeFragment f = new AccountSetupExchangeFragment(); 78 f.setSetupArguments(settingsMode); 79 return f; 80 } 81 82 /** 83 * Called to do initial creation of a fragment. This is called after 84 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 85 */ 86 @Override 87 public void onCreate(Bundle savedInstanceState) { 88 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 89 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreate"); 90 } 91 super.onCreate(savedInstanceState); 92 93 if (savedInstanceState != null) { 94 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 95 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 96 } 97 } 98 99 @Override 100 public View onCreateView(LayoutInflater inflater, ViewGroup container, 101 Bundle savedInstanceState) { 102 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 103 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); 104 } 105 int layoutId = mSettingsMode 106 ? R.layout.account_settings_exchange_fragment 107 : R.layout.account_setup_exchange_fragment; 108 109 View view = inflater.inflate(layoutId, container, false); 110 Context context = getActivity(); 111 112 mUsernameView = (EditText) view.findViewById(R.id.account_username); 113 mPasswordView = (EditText) view.findViewById(R.id.account_password); 114 mServerView = (EditText) view.findViewById(R.id.account_server); 115 mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); 116 mSslSecurityView.setOnCheckedChangeListener(this); 117 mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); 118 mTrustCertificatesDivider = view.findViewById(R.id.account_trust_certificates_divider); 119 120 // Calls validateFields() which enables or disables the Next button 121 // based on the fields' validity. 122 TextWatcher validationTextWatcher = new TextWatcher() { 123 public void afterTextChanged(Editable s) { 124 validateFields(); 125 } 126 127 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 128 public void onTextChanged(CharSequence s, int start, int before, int count) { } 129 }; 130 mUsernameView.addTextChangedListener(validationTextWatcher); 131 mPasswordView.addTextChangedListener(validationTextWatcher); 132 mServerView.addTextChangedListener(validationTextWatcher); 133 134 //EXCHANGE-REMOVE-SECTION-START 135 // Show device ID 136 try { 137 String deviceId = ExchangeService.getDeviceId(context); 138 ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); 139 } catch (IOException ignore) { 140 // There's nothing we can do here... 141 } 142 //EXCHANGE-REMOVE-SECTION-END 143 144 // Additional setup only used while in "settings" mode 145 onCreateViewSettingsMode(view); 146 147 return view; 148 } 149 150 @Override 151 public void onActivityCreated(Bundle savedInstanceState) { 152 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 153 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 154 } 155 super.onActivityCreated(savedInstanceState); 156 } 157 158 /** 159 * Called when the Fragment is visible to the user. 160 */ 161 @Override 162 public void onStart() { 163 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 164 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStart"); 165 } 166 super.onStart(); 167 mStarted = true; 168 loadSettings(SetupData.getAccount()); 169 } 170 171 /** 172 * Called when the fragment is visible to the user and actively running. 173 */ 174 @Override 175 public void onResume() { 176 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 177 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onResume"); 178 } 179 super.onResume(); 180 validateFields(); 181 } 182 183 @Override 184 public void onPause() { 185 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 186 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onPause"); 187 } 188 super.onPause(); 189 } 190 191 /** 192 * Called when the Fragment is no longer started. 193 */ 194 @Override 195 public void onStop() { 196 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 197 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStop"); 198 } 199 super.onStop(); 200 mStarted = false; 201 } 202 203 /** 204 * Called when the fragment is no longer in use. 205 */ 206 @Override 207 public void onDestroy() { 208 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 209 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onDestroy"); 210 } 211 super.onDestroy(); 212 } 213 214 @Override 215 public void onSaveInstanceState(Bundle outState) { 216 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 217 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState"); 218 } 219 super.onSaveInstanceState(outState); 220 221 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 222 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 223 } 224 225 /** 226 * Activity provides callbacks here. This also triggers loading and setting up the UX 227 */ 228 @Override 229 public void setCallback(Callback callback) { 230 super.setCallback(callback); 231 if (mStarted) { 232 loadSettings(SetupData.getAccount()); 233 } 234 } 235 236 /** 237 * Load the current settings into the UI 238 * 239 * @return true if the loaded values pass validation 240 */ 241 /* package */ boolean loadSettings(Account account) { 242 if (mLoaded) return validateFields(); 243 244 HostAuth hostAuth = account.mHostAuthRecv; 245 246 String userName = hostAuth.mLogin; 247 if (userName != null) { 248 // Add a backslash to the start of the username, but only if the username has no 249 // backslash in it. 250 if (userName.indexOf('\\') < 0) { 251 userName = "\\" + userName; 252 } 253 mUsernameView.setText(userName); 254 } 255 256 if (hostAuth.mPassword != null) { 257 mPasswordView.setText(hostAuth.mPassword); 258 } 259 260 String protocol = hostAuth.mProtocol; 261 if (protocol == null || !protocol.startsWith("eas")) { 262 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); 263 } 264 265 if (hostAuth.mAddress != null) { 266 mServerView.setText(hostAuth.mAddress); 267 } 268 269 boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); 270 boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); 271 mSslSecurityView.setChecked(ssl); 272 mTrustCertificatesView.setChecked(trustCertificates); 273 showTrustCertificates(ssl); 274 275 mLoaded = true; 276 return validateFields(); 277 } 278 279 private boolean usernameFieldValid(EditText usernameView) { 280 return Utility.isTextViewNotEmpty(usernameView) && 281 !usernameView.getText().toString().equals("\\"); 282 } 283 284 /** 285 * Check the values in the fields and decide if it makes sense to enable the "next" button 286 * @return true if all fields are valid, false if any fields are incomplete 287 */ 288 private boolean validateFields() { 289 if (!mLoaded) return false; 290 boolean enabled = usernameFieldValid(mUsernameView) 291 && Utility.isTextViewNotEmpty(mPasswordView) 292 && Utility.isTextViewNotEmpty(mServerView); 293 if (enabled) { 294 try { 295 URI uri = getUri(); 296 } catch (URISyntaxException use) { 297 enabled = false; 298 } 299 } 300 enableNextButton(enabled); 301 return enabled; 302 } 303 304 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 305 if (buttonView.getId() == R.id.account_ssl) { 306 showTrustCertificates(isChecked); 307 } 308 } 309 310 public void showTrustCertificates(boolean visible) { 311 int mode = visible ? View.VISIBLE : View.GONE; 312 mTrustCertificatesView.setVisibility(mode); 313 // Divider is optional (only on XL layouts) 314 if (mTrustCertificatesDivider != null) { 315 mTrustCertificatesDivider.setVisibility(mode); 316 } 317 } 318 319 /** 320 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 321 * 322 * TODO: Was the !isSaved() logic ever actually used? 323 */ 324 @Override 325 public void saveSettingsAfterEdit() { 326 Account account = SetupData.getAccount(); 327 if (account.isSaved()) { 328 // Account.update will NOT save the HostAuth's 329 account.update(mContext, account.toContentValues()); 330 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 331 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 332 if (account.mHostAuthRecv.mProtocol.equals("eas")) { 333 // For EAS, notify ExchangeService that the password has changed 334 try { 335 ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId); 336 } catch (RemoteException e) { 337 // Nothing to be done if this fails 338 } 339 } 340 } else { 341 // Account.save will save the HostAuth's 342 account.save(mContext); 343 } 344 // Update the backup (side copy) of the accounts 345 AccountBackupRestore.backupAccounts(mContext); 346 } 347 348 /** 349 * This entry point is not used (unlike in AccountSetupIncomingFragment) because the data 350 * is already saved by onNext(). 351 * TODO: Reconcile this, to make them more consistent. 352 */ 353 @Override 354 public void saveSettingsAfterSetup() { 355 } 356 357 /** 358 * Entry point from Activity after entering new settings and verifying them. For setup mode. 359 */ 360 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 361 Account account = SetupData.getAccount(); 362 account.mHostAuthSend = newHostAuth; 363 account.mHostAuthRecv = newHostAuth; 364 return loadSettings(account); 365 } 366 367 /** 368 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 369 * a problem with the user input. 370 * @return a URI built from the account setup fields 371 */ 372 /* package */ URI getUri() throws URISyntaxException { 373 boolean sslRequired = mSslSecurityView.isChecked(); 374 boolean trustCertificates = mTrustCertificatesView.isChecked(); 375 String scheme = (sslRequired) 376 ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") 377 : "eas"; 378 String userName = mUsernameView.getText().toString().trim(); 379 // Remove a leading backslash, if there is one, since we now automatically put one at 380 // the start of the username field 381 if (userName.startsWith("\\")) { 382 userName = userName.substring(1); 383 } 384 mCacheLoginCredential = userName; 385 String userInfo = userName + ":" + mPasswordView.getText(); 386 String host = mServerView.getText().toString().trim(); 387 String path = null; 388 389 URI uri = new URI( 390 scheme, 391 userInfo, 392 host, 393 0, 394 path, 395 null, 396 null); 397 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 try { 416 URI uri = getUri(); 417 Account setupAccount = SetupData.getAccount(); 418 setupAccount.setStoreUri(mContext, uri.toString()); 419 setupAccount.setSenderUri(mContext, uri.toString()); 420 421 // Stop here if the login credentials duplicate an existing account 422 // (unless they duplicate the existing account, as they of course will) 423 Account account = Utility.findExistingAccount(mContext, setupAccount.mId, 424 uri.getHost(), mCacheLoginCredential); 425 if (account != null) { 426 DuplicateAccountDialogFragment dialogFragment = 427 DuplicateAccountDialogFragment.newInstance(account.mDisplayName); 428 dialogFragment.show(getActivity(), DuplicateAccountDialogFragment.TAG); 429 return; 430 } 431 } catch (URISyntaxException use) { 432 /* 433 * It's unrecoverable if we cannot create a URI from components that 434 * we validated to be safe. 435 */ 436 throw new Error(use); 437 } 438 439 mCallback.onProceedNext(SetupData.CHECK_INCOMING, this); 440 } 441} 442