AccountSetupIncomingFragment.java revision 26ff609ebff3ef0f5ff841eb5d78c11edf8f0936
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 android.app.Activity; 20import android.app.FragmentManager; 21import android.app.FragmentTransaction; 22import android.content.Context; 23import android.os.Bundle; 24import android.text.Editable; 25import android.text.TextUtils; 26import android.text.TextWatcher; 27import android.text.method.DigitsKeyListener; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.inputmethod.EditorInfo; 32import android.widget.AdapterView; 33import android.widget.ArrayAdapter; 34import android.widget.EditText; 35import android.widget.Spinner; 36import android.widget.TextView; 37 38import com.android.email.R; 39import com.android.email.activity.UiUtilities; 40import com.android.email.activity.setup.AuthenticationView.AuthenticationCallback; 41import com.android.email.provider.AccountBackupRestore; 42import com.android.email.service.EmailServiceUtils; 43import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 44import com.android.email2.ui.MailActivityEmail; 45import com.android.emailcommon.Logging; 46import com.android.emailcommon.provider.Account; 47import com.android.emailcommon.provider.HostAuth; 48import com.android.emailcommon.utility.Utility; 49import com.android.mail.utils.LogUtils; 50 51import java.util.ArrayList; 52 53/** 54 * Provides UI for IMAP/POP account settings. 55 * 56 * This fragment is used by AccountSetupIncoming (for creating accounts) and by AccountSettingsXL 57 * (for editing existing accounts). 58 */ 59public class AccountSetupIncomingFragment extends AccountServerBaseFragment 60 implements AuthenticationCallback { 61 62 private final static String STATE_KEY_CREDENTIAL = "AccountSetupIncomingFragment.credential"; 63 private final static String STATE_KEY_LOADED = "AccountSetupIncomingFragment.loaded"; 64 65 private EditText mUsernameView; 66 private AuthenticationView mAuthenticationView; 67 private TextView mServerLabelView; 68 private EditText mServerView; 69 private EditText mPortView; 70 private Spinner mSecurityTypeView; 71 private TextView mDeletePolicyLabelView; 72 private Spinner mDeletePolicyView; 73 private View mImapPathPrefixSectionView; 74 private EditText mImapPathPrefixView; 75 // Delete policy as loaded from the device 76 private int mLoadedDeletePolicy; 77 78 private TextWatcher mValidationTextWatcher; 79 80 // Support for lifecycle 81 private boolean mStarted; 82 private boolean mLoaded; 83 private String mCacheLoginCredential; 84 private EmailServiceInfo mServiceInfo; 85 86 // Public no-args constructor needed for fragment re-instantiation 87 public AccountSetupIncomingFragment() {} 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 (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 96 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreate"); 97 } 98 super.onCreate(savedInstanceState); 99 100 if (savedInstanceState != null) { 101 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 102 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 103 } 104 } 105 106 @Override 107 public View onCreateView(LayoutInflater inflater, ViewGroup container, 108 Bundle savedInstanceState) { 109 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 110 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreateView"); 111 } 112 final int layoutId = mSettingsMode 113 ? R.layout.account_settings_incoming_fragment 114 : R.layout.account_setup_incoming_fragment; 115 116 final View view = inflater.inflate(layoutId, container, false); 117 118 mUsernameView = UiUtilities.getView(view, R.id.account_username); 119 mServerLabelView = UiUtilities.getView(view, R.id.account_server_label); 120 mServerView = UiUtilities.getView(view, R.id.account_server); 121 mPortView = UiUtilities.getView(view, R.id.account_port); 122 mSecurityTypeView = UiUtilities.getView(view, R.id.account_security_type); 123 mDeletePolicyLabelView = UiUtilities.getView(view, R.id.account_delete_policy_label); 124 mDeletePolicyView = UiUtilities.getView(view, R.id.account_delete_policy); 125 mImapPathPrefixSectionView = UiUtilities.getView(view, R.id.imap_path_prefix_section); 126 mImapPathPrefixView = UiUtilities.getView(view, R.id.imap_path_prefix); 127 mAuthenticationView = UiUtilities.getView(view, R.id.authentication_view); 128 129 // Updates the port when the user changes the security type. This allows 130 // us to show a reasonable default which the user can change. 131 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 132 @Override 133 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 134 updatePortFromSecurityType(); 135 } 136 137 @Override 138 public void onNothingSelected(AdapterView<?> arg0) { } 139 }); 140 141 // After any text edits, call validateFields() which enables or disables the Next button 142 mValidationTextWatcher = new TextWatcher() { 143 @Override 144 public void afterTextChanged(Editable s) { 145 validateFields(); 146 } 147 148 @Override 149 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 150 @Override 151 public void onTextChanged(CharSequence s, int start, int before, int count) { } 152 }; 153 // We're editing an existing account; don't allow modification of the user name 154 if (mSettingsMode) { 155 makeTextViewUneditable(mUsernameView, 156 getString(R.string.account_setup_username_uneditable_error)); 157 } 158 mUsernameView.addTextChangedListener(mValidationTextWatcher); 159 mServerView.addTextChangedListener(mValidationTextWatcher); 160 mPortView.addTextChangedListener(mValidationTextWatcher); 161 162 // Only allow digits in the port field. 163 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 164 165 // Additional setup only used while in "settings" mode 166 onCreateViewSettingsMode(view); 167 168 mAuthenticationView.setAuthenticationCallback(this); 169 170 return view; 171 } 172 173 @Override 174 public void onActivityCreated(Bundle savedInstanceState) { 175 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 176 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated"); 177 } 178 super.onActivityCreated(savedInstanceState); 179 180 final Context context = getActivity(); 181 final SetupDataFragment.SetupDataContainer container = 182 (SetupDataFragment.SetupDataContainer) context; 183 mSetupData = container.getSetupData(); 184 final Account account = mSetupData.getAccount(); 185 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 186 mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol); 187 188 if (mServiceInfo.offerLocalDeletes) { 189 SpinnerOption deletePolicies[] = { 190 new SpinnerOption(Account.DELETE_POLICY_NEVER, 191 context.getString( 192 R.string.account_setup_incoming_delete_policy_never_label)), 193 new SpinnerOption(Account.DELETE_POLICY_ON_DELETE, 194 context.getString( 195 R.string.account_setup_incoming_delete_policy_delete_label)), 196 }; 197 ArrayAdapter<SpinnerOption> deletePoliciesAdapter = 198 new ArrayAdapter<SpinnerOption>(context, 199 android.R.layout.simple_spinner_item, deletePolicies); 200 deletePoliciesAdapter.setDropDownViewResource( 201 android.R.layout.simple_spinner_dropdown_item); 202 mDeletePolicyView.setAdapter(deletePoliciesAdapter); 203 } 204 205 // Set up security type spinner 206 ArrayList<SpinnerOption> securityTypes = new ArrayList<SpinnerOption>(); 207 securityTypes.add( 208 new SpinnerOption(HostAuth.FLAG_NONE, context.getString( 209 R.string.account_setup_incoming_security_none_label))); 210 securityTypes.add( 211 new SpinnerOption(HostAuth.FLAG_SSL, context.getString( 212 R.string.account_setup_incoming_security_ssl_label))); 213 securityTypes.add( 214 new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString( 215 R.string.account_setup_incoming_security_ssl_trust_certificates_label))); 216 if (mServiceInfo.offerTls) { 217 securityTypes.add( 218 new SpinnerOption(HostAuth.FLAG_TLS, context.getString( 219 R.string.account_setup_incoming_security_tls_label))); 220 securityTypes.add(new SpinnerOption(HostAuth.FLAG_TLS | HostAuth.FLAG_TRUST_ALL, 221 context.getString(R.string 222 .account_setup_incoming_security_tls_trust_certificates_label))); 223 } 224 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>( 225 context, android.R.layout.simple_spinner_item, securityTypes); 226 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 227 mSecurityTypeView.setAdapter(securityTypesAdapter); 228 } 229 230 /** 231 * Called when the Fragment is visible to the user. 232 */ 233 @Override 234 public void onStart() { 235 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 236 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStart"); 237 } 238 super.onStart(); 239 mStarted = true; 240 configureEditor(); 241 loadSettings(); 242 } 243 244 /** 245 * Called when the fragment is visible to the user and actively running. 246 */ 247 @Override 248 public void onResume() { 249 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 250 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onResume"); 251 } 252 super.onResume(); 253 validateFields(); 254 } 255 256 @Override 257 public void onPause() { 258 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 259 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onPause"); 260 } 261 super.onPause(); 262 } 263 264 /** 265 * Called when the Fragment is no longer started. 266 */ 267 @Override 268 public void onStop() { 269 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 270 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStop"); 271 } 272 super.onStop(); 273 mStarted = false; 274 } 275 276 @Override 277 public void onDestroyView() { 278 // Make sure we don't get callbacks after the views are supposed to be destroyed 279 // and also don't hold onto them longer than we need 280 if (mUsernameView != null) { 281 mUsernameView.removeTextChangedListener(mValidationTextWatcher); 282 } 283 mUsernameView = null; 284 mServerLabelView = null; 285 if (mServerView != null) { 286 mServerView.removeTextChangedListener(mValidationTextWatcher); 287 } 288 mServerView = null; 289 if (mPortView != null) { 290 mPortView.removeTextChangedListener(mValidationTextWatcher); 291 } 292 mPortView = null; 293 if (mSecurityTypeView != null) { 294 mSecurityTypeView.setOnItemSelectedListener(null); 295 } 296 mSecurityTypeView = null; 297 mDeletePolicyLabelView = null; 298 mDeletePolicyView = null; 299 mImapPathPrefixSectionView = null; 300 mImapPathPrefixView = null; 301 302 super.onDestroyView(); 303 } 304 305 /** 306 * Called when the fragment is no longer in use. 307 */ 308 @Override 309 public void onDestroy() { 310 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 311 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onDestroy"); 312 } 313 super.onDestroy(); 314 } 315 316 @Override 317 public void onSaveInstanceState(Bundle outState) { 318 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 319 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onSaveInstanceState"); 320 } 321 super.onSaveInstanceState(outState); 322 323 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 324 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 325 } 326 327 /** 328 * Activity provides callbacks here. This also triggers loading and setting up the UX 329 */ 330 @Override 331 public void setCallback(Callback callback) { 332 super.setCallback(callback); 333 if (mStarted) { 334 configureEditor(); 335 loadSettings(); 336 } 337 } 338 339 /** 340 * Configure the editor for the account type 341 */ 342 private void configureEditor() { 343 final Account account = mSetupData.getAccount(); 344 if (account == null || account.mHostAuthRecv == null) { 345 LogUtils.e(Logging.LOG_TAG, 346 "null account or host auth. account null: %b host auth null: %b", 347 account == null, account == null || account.mHostAuthRecv == null); 348 return; 349 } 350 TextView lastView = mImapPathPrefixView; 351 mBaseScheme = account.mHostAuthRecv.mProtocol; 352 mServerLabelView.setText(R.string.account_setup_incoming_server_label); 353 mServerView.setContentDescription(getResources().getText( 354 R.string.account_setup_incoming_server_label)); 355 if (!mServiceInfo.offerPrefix) { 356 mImapPathPrefixSectionView.setVisibility(View.GONE); 357 lastView = mPortView; 358 } 359 if (!mServiceInfo.offerLocalDeletes) { 360 mDeletePolicyLabelView.setVisibility(View.GONE); 361 mDeletePolicyView.setVisibility(View.GONE); 362 mPortView.setImeOptions(EditorInfo.IME_ACTION_NEXT); 363 } 364 lastView.setOnEditorActionListener(mDismissImeOnDoneListener); 365 } 366 367 /** 368 * Load the current settings into the UI 369 */ 370 private void loadSettings() { 371 if (mLoaded) return; 372 373 final Account account = mSetupData.getAccount(); 374 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 375 mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol); 376 mAuthenticationView.setAuthInfo(mServiceInfo, recvAuth); 377 378 final String username = recvAuth.mLogin; 379 if (username != null) { 380 //*** For eas? 381 // Add a backslash to the start of the username, but only if the username has no 382 // backslash in it. 383 //if (userName.indexOf('\\') < 0) { 384 // userName = "\\" + userName; 385 //} 386 mUsernameView.setText(username); 387 } 388 389 if (mServiceInfo.offerPrefix) { 390 final String prefix = recvAuth.mDomain; 391 if (prefix != null && prefix.length() > 0) { 392 mImapPathPrefixView.setText(prefix.substring(1)); 393 } 394 } 395 396 // The delete policy is set for all legacy accounts. For POP3 accounts, the user sets 397 // the policy explicitly. For IMAP accounts, the policy is set when the Account object 398 // is created. @see AccountSetupBasics#populateSetupData 399 mLoadedDeletePolicy = account.getDeletePolicy(); 400 SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); 401 402 int flags = recvAuth.mFlags; 403 flags &= ~HostAuth.FLAG_AUTHENTICATE; 404 if (mServiceInfo.defaultSsl) { 405 flags |= HostAuth.FLAG_SSL; 406 } 407 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags); 408 409 final String hostname = recvAuth.mAddress; 410 if (hostname != null) { 411 mServerView.setText(hostname); 412 } 413 414 final int port = recvAuth.mPort; 415 if (port != HostAuth.PORT_UNKNOWN) { 416 mPortView.setText(Integer.toString(port)); 417 } else { 418 updatePortFromSecurityType(); 419 } 420 421 mLoadedRecvAuth = recvAuth; 422 mLoaded = true; 423 validateFields(); 424 } 425 426 /** 427 * Check the values in the fields and decide if it makes sense to enable the "next" button 428 */ 429 private void validateFields() { 430 if (!mLoaded) return; 431 enableNextButton(!TextUtils.isEmpty(mUsernameView.getText()) 432 && mAuthenticationView.getAuthValid() 433 && Utility.isServerNameValid(mServerView) 434 && Utility.isPortFieldValid(mPortView)); 435 436 mCacheLoginCredential = mUsernameView.getText().toString().trim(); 437 } 438 439 private int getPortFromSecurityType(boolean useSsl) { 440 final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, 441 mSetupData.getAccount().mHostAuthRecv.mProtocol); 442 return useSsl ? info.portSsl : info.port; 443 } 444 445 private boolean getSslSelected() { 446 final int securityType = 447 (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 448 return ((securityType & HostAuth.FLAG_SSL) != 0); 449 } 450 451 public void onUseSslChanged(boolean useSsl) { 452 mAuthenticationView.onUseSslChanged(useSsl); 453 } 454 455 private void updatePortFromSecurityType() { 456 final boolean sslSelected = getSslSelected(); 457 final int port = getPortFromSecurityType(sslSelected); 458 mPortView.setText(Integer.toString(port)); 459 onUseSslChanged(sslSelected); 460 } 461 462 /** 463 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 464 * Note, we update account here (as well as the account.mHostAuthRecv) because we edit 465 * account's delete policy here. 466 * Blocking - do not call from UI Thread. 467 */ 468 @Override 469 public void saveSettingsAfterEdit() { 470 final Account account = mSetupData.getAccount(); 471 account.update(mContext, account.toContentValues()); 472 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 473 // Update the backup (side copy) of the accounts 474 AccountBackupRestore.backup(mContext); 475 } 476 477 /** 478 * Entry point from Activity after entering new settings and verifying them. For setup mode. 479 */ 480 @Override 481 public void saveSettingsAfterSetup() { 482 final Account account = mSetupData.getAccount(); 483 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 484 final HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 485 486 // Set the username and password for the outgoing settings to the username and 487 // password the user just set for incoming. Use the verified host address to try and 488 // pick a smarter outgoing address. 489 final String hostName = 490 AccountSettingsUtils.inferServerName(mContext, recvAuth.mAddress, null, "smtp"); 491 sendAuth.setLogin(recvAuth.mLogin, recvAuth.mPassword); 492 sendAuth.setConnection(sendAuth.mProtocol, hostName, sendAuth.mPort, sendAuth.mFlags); 493 } 494 495 /** 496 * Entry point from Activity, when "next" button is clicked 497 */ 498 @Override 499 public void onNext() { 500 final Account account = mSetupData.getAccount(); 501 502 // Make sure delete policy is an valid option before using it; otherwise, the results are 503 // indeterminate, I suspect... 504 if (mDeletePolicyView.getVisibility() == View.VISIBLE) { 505 account.setDeletePolicy( 506 (Integer) ((SpinnerOption) mDeletePolicyView.getSelectedItem()).value); 507 } 508 509 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 510 final String userName = mUsernameView.getText().toString().trim(); 511 final String userPassword = mAuthenticationView.getPassword().toString(); 512 recvAuth.setLogin(userName, userPassword); 513 514 final String serverAddress = mServerView.getText().toString().trim(); 515 int serverPort; 516 try { 517 serverPort = Integer.parseInt(mPortView.getText().toString().trim()); 518 } catch (NumberFormatException e) { 519 serverPort = getPortFromSecurityType(getSslSelected()); 520 LogUtils.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); 521 } 522 final int securityType = 523 (Integer) ((SpinnerOption) mSecurityTypeView.getSelectedItem()).value; 524 recvAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType); 525 if (mServiceInfo.offerPrefix) { 526 final String prefix = mImapPathPrefixView.getText().toString().trim(); 527 recvAuth.mDomain = TextUtils.isEmpty(prefix) ? null : ("/" + prefix); 528 } else { 529 recvAuth.mDomain = null; 530 } 531 recvAuth.mClientCertAlias = mAuthenticationView.getClientCertificate(); 532 533 mCallback.onProceedNext(SetupDataFragment.CHECK_INCOMING, this); 534 clearButtonBounce(); 535 } 536 537 @Override 538 public boolean haveSettingsChanged() { 539 final boolean deletePolicyChanged; 540 541 // Only verify the delete policy if the control is visible (i.e. is a pop3 account) 542 if (mDeletePolicyView != null && mDeletePolicyView.getVisibility() == View.VISIBLE) { 543 int newDeletePolicy = 544 (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value; 545 deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy; 546 } else { 547 deletePolicyChanged = false; 548 } 549 550 return deletePolicyChanged || super.haveSettingsChanged(); 551 } 552 553 /** 554 * Implements AccountCheckSettingsFragment.Callbacks 555 */ 556 @Override 557 public void onAutoDiscoverComplete(int result, SetupDataFragment setupData) { 558 mSetupData = setupData; 559 final AccountSetupIncoming activity = (AccountSetupIncoming) getActivity(); 560 activity.onAutoDiscoverComplete(result, setupData); 561 } 562 563 @Override 564 public void onValidateStateChanged() { 565 validateFields(); 566 } 567} 568