AccountSetupExchangeFragment.java revision 858c2822777f74947e81476125590ad06bfe4803
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 /* 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 (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 79 Log.d(Email.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 } 88 89 @Override 90 public View onCreateView(LayoutInflater inflater, ViewGroup container, 91 Bundle savedInstanceState) { 92 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 93 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); 94 } 95 int layoutId = mSettingsMode 96 ? R.layout.account_settings_exchange_fragment 97 : R.layout.account_setup_exchange_fragment; 98 99 View view = inflater.inflate(layoutId, container, false); 100 Context context = getActivity(); 101 102 mUsernameView = (EditText) view.findViewById(R.id.account_username); 103 mPasswordView = (EditText) view.findViewById(R.id.account_password); 104 mServerView = (EditText) view.findViewById(R.id.account_server); 105 mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); 106 mSslSecurityView.setOnCheckedChangeListener(this); 107 mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); 108 mTrustCertificatesDivider = view.findViewById(R.id.account_trust_certificates_divider); 109 110 // Calls validateFields() which enables or disables the Next button 111 // based on the fields' validity. 112 TextWatcher validationTextWatcher = new TextWatcher() { 113 public void afterTextChanged(Editable s) { 114 validateFields(); 115 } 116 117 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 118 public void onTextChanged(CharSequence s, int start, int before, int count) { } 119 }; 120 mUsernameView.addTextChangedListener(validationTextWatcher); 121 mPasswordView.addTextChangedListener(validationTextWatcher); 122 mServerView.addTextChangedListener(validationTextWatcher); 123 124 //EXCHANGE-REMOVE-SECTION-START 125 // Show device ID 126 try { 127 String deviceId = ExchangeService.getDeviceId(context); 128 ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); 129 } catch (IOException ignore) { 130 // There's nothing we can do here... 131 } 132 //EXCHANGE-REMOVE-SECTION-END 133 134 // Additional setup only used while in "settings" mode 135 onCreateViewSettingsMode(view); 136 137 return view; 138 } 139 140 @Override 141 public void onActivityCreated(Bundle savedInstanceState) { 142 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 143 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 144 } 145 super.onActivityCreated(savedInstanceState); 146 } 147 148 /** 149 * Called when the Fragment is visible to the user. 150 */ 151 @Override 152 public void onStart() { 153 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 154 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStart"); 155 } 156 super.onStart(); 157 mStarted = true; 158 loadSettings(SetupData.getAccount()); 159 } 160 161 /** 162 * Called when the fragment is visible to the user and actively running. 163 */ 164 @Override 165 public void onResume() { 166 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 167 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onResume"); 168 } 169 super.onResume(); 170 validateFields(); 171 } 172 173 @Override 174 public void onPause() { 175 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 176 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onPause"); 177 } 178 super.onPause(); 179 } 180 181 /** 182 * Called when the Fragment is no longer started. 183 */ 184 @Override 185 public void onStop() { 186 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 187 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStop"); 188 } 189 super.onStop(); 190 mStarted = false; 191 } 192 193 /** 194 * Called when the fragment is no longer in use. 195 */ 196 @Override 197 public void onDestroy() { 198 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 199 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onDestroy"); 200 } 201 super.onDestroy(); 202 } 203 204 @Override 205 public void onSaveInstanceState(Bundle outState) { 206 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 207 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState"); 208 } 209 super.onSaveInstanceState(outState); 210 211 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 212 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 213 } 214 215 /** 216 * Activity provides callbacks here. This also triggers loading and setting up the UX 217 */ 218 @Override 219 public void setCallback(Callback callback) { 220 super.setCallback(callback); 221 if (mStarted) { 222 loadSettings(SetupData.getAccount()); 223 } 224 } 225 226 /** 227 * Load the current settings into the UI 228 * 229 * @return true if the loaded values pass validation 230 */ 231 /* package */ boolean loadSettings(Account account) { 232 if (mLoaded) return validateFields(); 233 234 HostAuth hostAuth = account.mHostAuthRecv; 235 236 String userName = hostAuth.mLogin; 237 if (userName != null) { 238 // Add a backslash to the start of the username, but only if the username has no 239 // backslash in it. 240 if (userName.indexOf('\\') < 0) { 241 userName = "\\" + userName; 242 } 243 mUsernameView.setText(userName); 244 } 245 246 if (hostAuth.mPassword != null) { 247 mPasswordView.setText(hostAuth.mPassword); 248 } 249 250 String protocol = hostAuth.mProtocol; 251 if (protocol == null || !protocol.startsWith("eas")) { 252 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); 253 } 254 255 if (hostAuth.mAddress != null) { 256 mServerView.setText(hostAuth.mAddress); 257 } 258 259 boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); 260 boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); 261 mSslSecurityView.setChecked(ssl); 262 mTrustCertificatesView.setChecked(trustCertificates); 263 showTrustCertificates(ssl); 264 265 try { 266 mLoadedUri = getUri(); 267 } catch (URISyntaxException ignore) { 268 // ignore; should not happen 269 } 270 271 mLoaded = true; 272 return validateFields(); 273 } 274 275 private boolean usernameFieldValid(EditText usernameView) { 276 return Utility.isTextViewNotEmpty(usernameView) && 277 !usernameView.getText().toString().equals("\\"); 278 } 279 280 /** 281 * Check the values in the fields and decide if it makes sense to enable the "next" button 282 * @return true if all fields are valid, false if any fields are incomplete 283 */ 284 private boolean validateFields() { 285 if (!mLoaded) return false; 286 boolean enabled = usernameFieldValid(mUsernameView) 287 && Utility.isTextViewNotEmpty(mPasswordView) 288 && Utility.isTextViewNotEmpty(mServerView); 289 if (enabled) { 290 try { 291 URI uri = getUri(); 292 } catch (URISyntaxException use) { 293 enabled = false; 294 } 295 } 296 enableNextButton(enabled); 297 298 // Warn (but don't prevent) if password has leading/trailing spaces 299 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 300 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 * 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.backupAccounts(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 return loadSettings(account); 353 } 354 355 /** 356 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 357 * a problem with the user input. 358 * @return a URI built from the account setup fields 359 */ 360 @Override 361 protected URI getUri() throws URISyntaxException { 362 boolean sslRequired = mSslSecurityView.isChecked(); 363 boolean trustCertificates = mTrustCertificatesView.isChecked(); 364 String scheme = (sslRequired) 365 ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") 366 : "eas"; 367 String userName = mUsernameView.getText().toString().trim(); 368 // Remove a leading backslash, if there is one, since we now automatically put one at 369 // the start of the username field 370 if (userName.startsWith("\\")) { 371 userName = userName.substring(1); 372 } 373 mCacheLoginCredential = userName; 374 String userInfo = userName + ":" + mPasswordView.getText(); 375 String host = mServerView.getText().toString().trim(); 376 String path = null; 377 378 URI uri = new URI( 379 scheme, 380 userInfo, 381 host, 382 0, 383 path, 384 null, 385 null); 386 387 return uri; 388 } 389 390 /** 391 * Implements AccountCheckSettingsFragment.Callbacks 392 */ 393 @Override 394 public void onAutoDiscoverComplete(int result, HostAuth hostAuth) { 395 AccountSetupExchange activity = (AccountSetupExchange) getActivity(); 396 activity.onAutoDiscoverComplete(result, hostAuth); 397 } 398 399 /** 400 * Entry point from Activity, when "next" button is clicked 401 */ 402 @Override 403 public void onNext() { 404 try { 405 URI uri = getUri(); 406 Account setupAccount = SetupData.getAccount(); 407 setupAccount.setStoreUri(mContext, uri.toString()); 408 setupAccount.setSenderUri(mContext, uri.toString()); 409 410 // Check for a duplicate account (requires async DB work) and if OK, proceed with check 411 startDuplicateTaskCheck(setupAccount.mId, uri.getHost(), mCacheLoginCredential, 412 SetupData.CHECK_INCOMING); 413 } catch (URISyntaxException use) { 414 /* 415 * It's unrecoverable if we cannot create a URI from components that 416 * we validated to be safe. 417 */ 418 throw new Error(use); 419 } 420 } 421} 422