AccountSetupExchangeFragment.java revision 7b5c5cf2a993d68bb8fe1a65bde3184e8d59c10f
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.SyncManager; 27 28import android.app.Activity; 29import android.app.Fragment; 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 * TODO: The manifest for this activity has it ignore config changes, because 53 * we don't want to restart on every orientation - this would launch autodiscover again. 54 * Do not attempt to define orientation-specific resources, they won't be loaded. 55 * What we really need here is a more "sticky" way to prevent that problem. 56 */ 57public class AccountSetupExchangeFragment extends Fragment implements OnCheckedChangeListener { 58 59 private final static String STATE_KEY_CREDENTIAL = 60 "AccountSetupExchangeFragment.loginCredential"; 61 62 private EditText mUsernameView; 63 private EditText mPasswordView; 64 private EditText mServerView; 65 private CheckBox mSslSecurityView; 66 private CheckBox mTrustCertificatesView; 67 68 // Support for lifecycle 69 private Context mContext; 70 private Callback mCallback = EmptyCallback.INSTANCE; 71 private boolean mStarted; 72 private boolean mLoaded; 73 private String mCacheLoginCredential; 74 75 /** 76 * Callback interface that owning activities must implement 77 */ 78 public interface Callback { 79 public void onEnableProceedButtons(boolean enable); 80 public void onProceedNext(); 81 } 82 83 private static class EmptyCallback implements Callback { 84 public static final Callback INSTANCE = new EmptyCallback(); 85 @Override public void onProceedNext() { } 86 @Override public void onEnableProceedButtons(boolean enable) { } 87 } 88 89 /** 90 * Called to do initial creation of a fragment. This is called after 91 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 92 */ 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 96 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreate"); 97 } 98 super.onCreate(savedInstanceState); 99 100 if (savedInstanceState != null) { 101 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 102 } 103 } 104 105 @Override 106 public View onCreateView(LayoutInflater inflater, ViewGroup container, 107 Bundle savedInstanceState) { 108 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 109 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onCreateView"); 110 } 111 View view = inflater.inflate(R.layout.account_setup_exchange_fragment, container, false); 112 Context context = getActivity(); 113 114 mUsernameView = (EditText) view.findViewById(R.id.account_username); 115 mPasswordView = (EditText) view.findViewById(R.id.account_password); 116 mServerView = (EditText) view.findViewById(R.id.account_server); 117 mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl); 118 mSslSecurityView.setOnCheckedChangeListener(this); 119 mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates); 120 121 // Calls validateFields() which enables or disables the Next button 122 // based on the fields' validity. 123 TextWatcher validationTextWatcher = new TextWatcher() { 124 public void afterTextChanged(Editable s) { 125 validateFields(); 126 } 127 128 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 129 public void onTextChanged(CharSequence s, int start, int before, int count) { } 130 }; 131 mUsernameView.addTextChangedListener(validationTextWatcher); 132 mPasswordView.addTextChangedListener(validationTextWatcher); 133 mServerView.addTextChangedListener(validationTextWatcher); 134 135 //EXCHANGE-REMOVE-SECTION-START 136 // Show device ID 137 try { 138 String deviceId = SyncManager.getDeviceId(context); 139 ((TextView) view.findViewById(R.id.device_id)).setText(deviceId); 140 } catch (IOException ignore) { 141 // There's nothing we can do here... 142 } 143 //EXCHANGE-REMOVE-SECTION-END 144 145 return view; 146 } 147 148 @Override 149 public void onActivityCreated(Bundle savedInstanceState) { 150 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 151 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated"); 152 } 153 super.onActivityCreated(savedInstanceState); 154 } 155 156 /** 157 * Called when the Fragment is visible to the user. 158 */ 159 @Override 160 public void onStart() { 161 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 162 Log.d(Email.LOG_TAG, "AccountSetupExchangeFragment onStart"); 163 } 164 super.onStart(); 165 mStarted = true; 166 if (!mLoaded) { 167 loadSettings(SetupData.getAccount()); 168 } 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 } 223 224 /** 225 * Activity provides callbacks here. This also triggers loading and setting up the UX 226 */ 227 public void setCallback(Callback callback) { 228 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 229 mContext = getActivity(); 230 if (mStarted && !mLoaded) { 231 loadSettings(SetupData.getAccount()); 232 } 233 } 234 235 /** 236 * Load the current settings into the UI 237 * 238 * @return true if the loaded values pass validation 239 */ 240 /* package */ boolean loadSettings(Account account) { 241 HostAuth hostAuth = account.mHostAuthRecv; 242 243 String userName = hostAuth.mLogin; 244 if (userName != null) { 245 // Add a backslash to the start of the username, but only if the username has no 246 // backslash in it. 247 if (userName.indexOf('\\') < 0) { 248 userName = "\\" + userName; 249 } 250 mUsernameView.setText(userName); 251 } 252 253 if (hostAuth.mPassword != null) { 254 mPasswordView.setText(hostAuth.mPassword); 255 } 256 257 String protocol = hostAuth.mProtocol; 258 if (protocol == null || !protocol.startsWith("eas")) { 259 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); 260 } 261 262 if (hostAuth.mAddress != null) { 263 mServerView.setText(hostAuth.mAddress); 264 } 265 266 boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL); 267 boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES); 268 mSslSecurityView.setChecked(ssl); 269 mTrustCertificatesView.setChecked(trustCertificates); 270 mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE); 271 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 boolean enabled = usernameFieldValid(mUsernameView) 286 && Utility.isTextViewNotEmpty(mPasswordView) 287 && Utility.isTextViewNotEmpty(mServerView); 288 if (enabled) { 289 try { 290 URI uri = getUri(); 291 } catch (URISyntaxException use) { 292 enabled = false; 293 } 294 } 295 mCallback.onEnableProceedButtons(enabled); 296 return enabled; 297 } 298 299 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 300 if (buttonView.getId() == R.id.account_ssl) { 301 mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE); 302 } 303 } 304 /** 305 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 306 * 307 * TODO: Was the !isSaved() logic ever actually used? 308 */ 309 public void saveSettingsAfterEdit() { 310 Account account = SetupData.getAccount(); 311 if (account.isSaved()) { 312 // Account.update will NOT save the HostAuth's 313 account.update(mContext, account.toContentValues()); 314 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 315 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 316 if (account.mHostAuthRecv.mProtocol.equals("eas")) { 317 // For EAS, notify SyncManager that the password has changed 318 try { 319 ExchangeUtils.getExchangeEmailService(mContext, null).hostChanged(account.mId); 320 } catch (RemoteException e) { 321 // Nothing to be done if this fails 322 } 323 } 324 } else { 325 // Account.save will save the HostAuth's 326 account.save(mContext); 327 } 328 // Update the backup (side copy) of the accounts 329 AccountBackupRestore.backupAccounts(mContext); 330 } 331 332 /** 333 * Entry point from Activity after entering new settings and verifying them. For setup mode. 334 */ 335 public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) { 336 Account account = SetupData.getAccount(); 337 account.mHostAuthSend = newHostAuth; 338 account.mHostAuthRecv = newHostAuth; 339 return loadSettings(account); 340 } 341 342 /** 343 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 344 * a problem with the user input. 345 * @return a URI built from the account setup fields 346 */ 347 private URI getUri() throws URISyntaxException { 348 boolean sslRequired = mSslSecurityView.isChecked(); 349 boolean trustCertificates = mTrustCertificatesView.isChecked(); 350 String scheme = (sslRequired) 351 ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+") 352 : "eas"; 353 String userName = mUsernameView.getText().toString().trim(); 354 // Remove a leading backslash, if there is one, since we now automatically put one at 355 // the start of the username field 356 if (userName.startsWith("\\")) { 357 userName = userName.substring(1); 358 } 359 mCacheLoginCredential = userName; 360 String userInfo = userName + ":" + mPasswordView.getText().toString().trim(); 361 String host = mServerView.getText().toString().trim(); 362 String path = null; 363 364 URI uri = new URI( 365 scheme, 366 userInfo, 367 host, 368 0, 369 path, 370 null, 371 null); 372 373 return uri; 374 } 375 376 /** 377 * Entry point from Activity, when "next" button is clicked 378 */ 379 public void onNext() { 380 try { 381 URI uri = getUri(); 382 Account setupAccount = SetupData.getAccount(); 383 setupAccount.setStoreUri(mContext, uri.toString()); 384 setupAccount.setSenderUri(mContext, uri.toString()); 385 386 // Stop here if the login credentials duplicate an existing account 387 // (unless they duplicate the existing account, as they of course will) 388 Account account = Utility.findExistingAccount(mContext, setupAccount.mId, 389 uri.getHost(), mCacheLoginCredential); 390 if (account != null) { 391 DuplicateAccountDialogFragment dialogFragment = 392 DuplicateAccountDialogFragment.newInstance(account.mDisplayName); 393 dialogFragment.show(getActivity(), DuplicateAccountDialogFragment.TAG); 394 return; 395 } 396 } catch (URISyntaxException use) { 397 /* 398 * It's unrecoverable if we cannot create a URI from components that 399 * we validated to be safe. 400 */ 401 throw new Error(use); 402 } 403 404 mCallback.onProceedNext(); 405 } 406} 407