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