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